The LAMP Stack Explained
LAMP combines Linux, Apache, MySQL and PHP to run database-backed websites. Linux runs the server, Apache handles web requests, MySQL stores data and PHP builds the response.
Debugging LAMP is easier when you know which component failed. A browser does not query MySQL directly, and MySQL does not send HTML. Each request moves through the stack in order, then the response travels back to the browser.
What Does Each Letter in LAMP Mean?
LAMP stands for Linux, Apache, MySQL and PHP. Each component has a different job in serving a web page.
| LAMP Stack Letter | Component Responsibility in the Request |
|---|---|
| Linux | Runs the operating system, processes, files, networking, users, and permissions underneath the application. |
| Apache | Listens for HTTP requests, selects a virtual host and file or handler, and returns the resulting HTTP response. |
| MySQL | Stores structured application data and enforces database permissions, indexes, constraints, and transactions. |
| PHP | Runs application code, validates the request, queries MySQL, and renders the HTML or other response body. |
Linux supports the other processes, but it does not decide which PHP file handles a URL. Apache owns the HTTP request boundary and routing configuration. PHP owns the application's behavior and generated response. MySQL owns stored data and the rules attached to it.
The parts can change without changing the application's basic job. MariaDB can fill the database role, and PHP can run through Apache's PHP module or through PHP-FPM. The name LAMP describes the classic combination, while the boundaries explain how current installations still work.
How Does a LAMP Stack Handle a Web Request?
In a LAMP stack, Apache receives the request, PHP runs the application code and queries MySQL when needed, then Apache returns PHP’s response to the browser. A request for https://example.test/posts/1, for example, follows this path through the four components:
- Linux accepts the network traffic and runs the Apache process.
- Apache matches the hostname and path to a virtual host and PHP entry point.
- PHP reads the route, validates input, and sends a query through a database driver such as PDO.
- MySQL checks the account's permissions, runs the query, and returns rows.
- PHP escapes the data and builds an HTML response.
- Apache sends the HTTP status, headers, and body back to the browser.
The browser now has the generated response. When a page fails, identify the last boundary that worked instead of treating LAMP as one large program.
A connection error points toward PHP, the MySQL listener, credentials, or database permissions. A raw PHP source file in the browser points toward Apache's PHP handler. A 403 response may come from Apache access rules or from the PHP application itself. Check the logs to find which component rejected the request.
HTTP status codes help locate the failing boundary. A 404 means Apache or the application could not map the requested path. A 500 means a server-side component failed after accepting the request. A successful 200 response with missing data points farther into PHP rendering or the SQL result.
When Should You Use a LAMP Stack?
LAMP suits PHP websites, content management systems and applications that store relational data. It is especially practical when your hosting already supports Apache and MySQL. Use LEMP if you prefer Nginx instead of Apache; Windows and macOS variants are often called WAMP and MAMP. Managed hosting can handle server maintenance if you would rather focus on the application.
Build a Minimal PHP and MySQL Page
This example creates one MySQL table, queries it from PHP with PDO, and lets Apache serve the resulting index.php. It assumes the operating system packages for Apache, PHP, the PDO MySQL driver, and MySQL are already installed.
Create the MySQL Data
Create a small database and a row that the page can display:
CREATE DATABASE lamp_demo
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE lamp_demo;
CREATE TABLE posts (
id BIGINT UNSIGNED PRIMARY KEY,
title VARCHAR(200) NOT NULL
);
INSERT INTO posts (id, title)
VALUES (1, 'Your first LAMP page'); The web application needs an account with only the permissions it uses. MySQL checks privileges for each statement, so a read-only page does not need an administrative account.
CREATE USER 'lamp_reader'@'127.0.0.1'
IDENTIFIED BY 'replace-with-a-generated-secret';
GRANT SELECT ON lamp_demo.*
TO 'lamp_reader'@'127.0.0.1'; Supply the real password through the server's secret or environment configuration. Do not place it inside the public PHP file or commit it with the project.
Query MySQL from PHP
Place the example file at /srv/lamp-demo/public/index.php inside the public document root. PDO opens the connection, runs a fixed query, fetches one associative row, and escapes the title before it enters HTML.
<?php
declare(strict_types=1);
$password = getenv("DB_PASSWORD");
if ($password === false) {
throw new RuntimeException("DB_PASSWORD is not configured.");
}
$pdo = new PDO(
"mysql:host=127.0.0.1;dbname=lamp_demo;charset=utf8mb4",
"lamp_reader",
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
],
);
$statement = $pdo->query(
"SELECT id, title FROM posts WHERE id = 1"
);
$post = $statement->fetch();
if ($post === false) {
http_response_code(404);
exit("Post not found");
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= htmlspecialchars(
$post["title"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8",
) ?></title>
</head>
<body>
<h1><?= htmlspecialchars(
$post["title"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8",
) ?></h1>
</body>
</html> A request for the page should return an H1 containing Your first LAMP page. The browser receives only the rendered HTML. It never receives the password, PDO object, or SQL statement.
Serve the Page with Apache
An Apache virtual host connects the hostname to the public directory. The exact configuration directory depends on the operating system package. This Debian or Ubuntu example uses the distribution's explicit Apache log path:
<VirtualHost *:80>
ServerName example.test
DocumentRoot "/srv/lamp-demo/public"
DirectoryIndex index.php
<Directory "/srv/lamp-demo/public">
AllowOverride None
Require all granted
</Directory>
ErrorLog "/var/log/apache2/lamp-demo-error.log"
CustomLog "/var/log/apache2/lamp-demo-access.log" combined
</VirtualHost> This configuration tells Apache where the public files live and permits requests to that directory. It does not install PHP or choose its execution model. Apache still needs either a working PHP module or a FastCGI mapping to PHP-FPM.
For PHP-FPM, add the password to the active pool configuration so getenv("DB_PASSWORD") can read it. Replace the placeholder with the deployment secret:
env[DB_PASSWORD] = replace-with-a-generated-secret When Apache runs PHP through mod_php, the equivalent mechanism is SetEnv DB_PASSWORD "replace-with-a-generated-secret" in the virtual host. Keep the real value out of the public document root and version control in either model.
After reloading Apache, request the page and inspect the response:
curl -i -H "Host: example.test" http://127.0.0.1/
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
<!doctype html>
...
<h1>Your first LAMP page</h1> A successful response proves the whole path: Apache found the virtual host, PHP executed, PDO connected, MySQL returned a row, and Apache sent the rendered result.
Choose the Apache Module or PHP-FPM
mod_php loads the PHP interpreter into Apache processes. Apache recognizes a PHP file, hands it to the embedded PHP handler, and receives the generated output inside the same process model.
PHP-FPM runs PHP workers as a separate service. Apache's mod_proxy_fcgi can forward PHP requests to an FPM socket or TCP listener, then return the generated response. The PHP manual describes FPM pools that can use different users, groups, environments, limits, and php.ini settings.
<FilesMatch "\.php$">
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/"
</FilesMatch> The socket path varies by operating system and PHP package, so copy it from the active FPM pool's listen setting. Apache also needs mod_proxy and mod_proxy_fcgi enabled.
Choose one execution path and document it. Loading mod_php while also forwarding .php files to FPM creates confusing configuration and can make the command-line PHP version, Apache module version, and FPM version disagree.
Modern LAMP: Containers, Managed Hosting, and LEMP
Containers and managed hosting are the operational successors to assembling every LAMP component on one manually administered server. They keep the same responsibilities and package them differently or move part of the maintenance to a provider.
A container deployment often runs the web server, PHP-FPM, and MySQL as separate services. The database needs persistent storage, the application needs secrets, and the services need health checks and a private network. Replacing one server with several containers does not make those boundaries disappear.
Managed PHP hosting can handle Linux updates, web-server configuration, TLS, backups, and database operations. Check exactly which jobs the provider owns. Application updates, database queries, credentials, and error handling usually remain the application's responsibility.
Replacing Apache with nginx creates the common LEMP variant. Nginx accepts the HTTP request and forwards PHP files to PHP-FPM, while Linux, MySQL, and PHP keep their original roles. Apache with PHP-FPM is also a current design, so adopting FPM does not require changing web servers.
Choose the deployment model from operational needs. A managed host reduces routine server administration, while containers make service boundaries and deployment artifacts explicit. A small site can still run well on one maintained server when its patching, backups, monitoring, and recovery responsibilities have clear owners.
Common Pitfalls & Debugging
PHP Works in the Terminal but Fails in Apache
The command-line interface, mod_php, and PHP-FPM are different server APIs and can load different configuration files. A successful php index.php test proves the CLI works, but it does not prove Apache has a PHP handler.
Create a temporary diagnostic page that prints PHP_SAPI and php_ini_loaded_file(), request it through Apache, record the result, and delete the page. If the browser downloads the file or displays PHP source, fix the Apache handler immediately and keep the site unavailable until source disclosure is closed.
<?php
header("Content-Type: text/plain");
echo "SAPI: ", PHP_SAPI, PHP_EOL;
echo "php.ini: ", php_ini_loaded_file() ?: "none", PHP_EOL; For FPM, confirm that the service is running and that Apache's socket path matches the pool's listen setting. For mod_php, confirm that the module is loaded and the PHP file handler is active.
Apache Returns a 403 Forbidden Response
A 403 response commonly comes from one of two permission layers. Apache authorization must allow the directory, and the operating-system user running Apache must be able to traverse the parent directories and read the public files.
Check the matching <Directory> block and its Require directive, then check filesystem ownership and modes. With PHP-FPM, the pool user also needs access to the script. Do not solve the problem with chmod 777, because world-writable application files create a separate security failure.
Database permission errors come from a later boundary in the request. An Access denied message from MySQL means the PHP code reached the database server, but the account, host match, password, or grants did not authorize the connection or query.
The Page Is Blank or Returns a 500 Error
Start with the configured logs instead of guessing at default file paths. Package layouts differ, and containers may send logs to standard output or a platform collector.
- Apache writes request failures to the destination set by its
ErrorLogdirective. - PHP writes errors to the configured
error_logwhenlog_errorsis enabled. - PHP-FPM has a global
error_log, and a pool can set its own PHP error log. - MySQL records server startup and runtime problems in its configured error log, with recent events also available through Performance Schema when that output is enabled.
Keep display_errors enabled only in a private development environment. The PHP manual warns that displayed production errors can expose confidential details, including database credentials. Production should log the full error and return a generic response to the browser.
Production Baseline
- Keep Linux, Apache, PHP, and MySQL on supported, patched releases.
- Expose only the public document root, with configuration and secrets stored outside it.
- Give the MySQL application account only the privileges required by the application.
- Use PDO prepared statements for request values and escape database text when rendering HTML.
- Disable displayed PHP errors in production and verify that Apache, PHP, FPM, and MySQL logs reach an operator.
- Back up the database and test a restore before treating the backup job as complete.
- Terminate HTTPS at Apache, a trusted proxy, or the managed platform, and document which layer owns it.
The stack is dependable when each component has a clear job and an observable failure path. A plain request, one PHP entry point, one least-privilege database account, and known log destinations make a better starting point than a large local bundle nobody can explain.
Frequently Asked Questions
Is the LAMP stack outdated?
The LAMP stack is not inherently outdated. Its supported components receive releases and security updates, and the request model still suits content sites and database-backed web applications. The deployment style has changed, with PHP-FPM, containers, and managed services replacing many hand-built server installations.
Does a LAMP server need a separate application server?
A typical LAMP application does not need another application server. Apache and PHP-FPM can handle its HTTP requests, though background jobs or real-time features may need separate workers.
Can one server run several PHP versions at once?
One server can run multiple PHP versions using a separate PHP-FPM installation or service for each version. Configure each service with its own socket, then route each site to the appropriate socket. Pools within one FPM service use that service’s PHP version.
Does moving from Apache to nginx change the PHP code?
Moving from Apache to Nginx usually leaves the PHP application code unchanged when both use PHP-FPM. Translate Apache-specific routing, rewrite and access rules into Nginx configuration, and check any application assumptions about server variables.
Does PHP-FPM run as the same operating-system user as Apache or nginx?
PHP-FPM can run as a different operating-system user from Apache or Nginx. A dedicated pool user helps limit access to other applications’ files, provided filesystem permissions are configured accordingly.
Can a GUI tool like phpMyAdmin still connect to a MySQL database running in Docker?
phpMyAdmin can connect to MySQL in Docker over a shared private container network. A GUI on the host needs a reachable MySQL port, which can be bound to localhost. Neither arrangement requires exposing the database to the public internet.
Self-Check
- Which LAMP component accepts the HTTP request and selects the virtual host: Linux, Apache, MySQL, or PHP?
- What happens when the PHP example runs without
DB_PASSWORDin the FPM pool or Apache environment? - Which MySQL account should the read-only page use:
root,lamp_reader, or an anonymous account? - What does the successful curl response prove about the request path through the stack?
- If PHP works in the terminal but Apache returns raw PHP source, which boundary should be checked first?
Answers
- Apache. It owns the HTTP boundary, chooses the matching virtual host, and passes PHP files to the configured handler.
- PHP throws a RuntimeException. The guard stops the page before PDO attempts a connection with a missing secret.
lamp_reader. Its SELECT-only grant matches the page's database work and avoids administrative privileges.- Every boundary completed. Apache found the host, PHP executed, PDO connected, MySQL returned the row, and Apache sent the rendered HTML.
- Apache's PHP handler. The CLI and web server use different server APIs, so a working CLI does not prove that mod_php or PHP-FPM is configured.
Next Steps
Start with the PHP tutorials hub, then build the database boundary with PHP and MySQL with PDO. The SQL tutorials hub covers queries and schema design, while PHP security fundamentals covers the input, output, session, and deployment checks around the example.
Sources
-
[1]
PHP Installation and Configuration(php.net)
-
[2]
PHP FastCGI Process Manager(php.net)
-
[3]
PHP-FPM Configuration(php.net)
- [4]
-
[5]
PHP Error Basics(php.net)
-
[6]
Apache HTTP Server Getting Started(httpd.apache.org)
-
[7]
Apache mod_proxy_fcgi(httpd.apache.org)
-
[8]
Apache ErrorLog Directive(httpd.apache.org)
-
[9]
MySQL Access Control and Account Management(dev.mysql.com)
-
[10]
MySQL Server Logs(dev.mysql.com)
-
[11]
MySQL Performance Schema error_log Table(dev.mysql.com)
Read Next
Use PDO prepared statements to connect PHP to MySQL safely and keep SQL input handling clear.
A beginner's introduction to SQL: whether you still need it with ORMs and AI, SQL or NoSQL, which database to start with, who uses which and why, and a roadmap to every SQL guide.
Learn where MySQL fits, how to create a restricted application user, how InnoDB transactions work, and how to plan backups and upgrades.
A beginner's introduction to PHP: whether it is worth learning in 2026, what runs on it, PHP or Node, whether it is secure, how coding agents change the work, and a roadmap to every PHP guide.