LEMP Stack Production Setup: Pre-Deploy Checklist

- What makes a LEMP stack ready for production?
- Start with the effective system, not remembered defaults
- Prove recovery before changing production settings
- Reduce exposure at every network boundary
- Make security updates an operation, not an intention
- Prove Nginx routes only the intended hosts
- Treat TLS renewal as part of the service
- Size PHP-FPM from measurements
- Give the application the smallest database role it needs
- Make the deployment reversible
- Monitor the entire request path
- Run one launch rehearsal
- Sources
What makes a LEMP stack ready for production?
A production-ready LEMP stack is a system you can inventory, restrict, observe, recover, and roll back. Running Nginx, PHP-FPM, and MySQL or MariaDB proves only that the components start. Before real traffic arrives, require evidence for recovery, network exposure, updates, host routing, TLS renewal, PHP behavior, database access, logging, monitoring, and rollback.
This checklist deliberately contains no shell commands. No Ubuntu 24.04 test host was available for testing commands as written, so publishing copy-and-paste steps would be guesswork. The linked implementation guides carry the procedures; this page defines the production gates those procedures must satisfy.
| Gate | Evidence to keep | Failure it prevents |
|---|---|---|
| Inventory | OS release, package origins, installed versions, active configuration | Tuning the wrong service or file |
| Recovery | A completed restore rehearsal | Discovering that a backup cannot be restored |
| Exposure | Listener and firewall review | Publishing an administrative service |
| Updates | Update history and maintenance owner | Leaving known fixes unapplied |
| Nginx | Host-routing and configuration-validation results | Serving the wrong site or invalid configuration |
| TLS | Renewal test and expiry alert | An avoidable certificate outage |
| PHP-FPM | Pool, error, queue, and capacity evidence | Data disclosure or worker exhaustion |
| Database | Account, privilege, listener, and restore review | Excessive access or unrecoverable data |
| Deployment | Release identifier and rollback rehearsal | A one-way release |
| Monitoring | External checks and routed alerts | A quiet failure |
Start with the effective system, not remembered defaults
Record the operating-system release, installed package versions, package sources, enabled services, and listening network sockets. Then record the effective Nginx configuration, the PHP-FPM pools and configuration inputs, and the exact database product and edition. “LEMP” does not identify those facts.
Do not infer a PHP socket name, service name, configuration path, process-manager mode, database bind address, or package version from a tutorial. Nginx itself documents several possible configuration locations because installation methods differ. MySQL documents how to inspect the values used by the running server. The relevant evidence is what this server loaded, not what a sample file contains.
If the machine is not installed yet, the Ubuntu 24.04 LEMP installation guide covers the installation sequence. Return here after installation and inventory the resulting system.
Prove recovery before changing production settings
A backup job reporting success is not recovery evidence. Define what must be recoverable: application data, database contents, uploaded files, configuration, secrets, and any metadata needed to resume service.
Run a restoration rehearsal into an isolated target that cannot overwrite production. Confirm the application can read the restored data, not merely that an archive can be opened. Record the restore duration you actually observed instead of promising a recovery time you have not measured.
MySQL’s security documentation explicitly calls for an appropriate recovery solution and a test that information can be recovered from backups. Choose the documented procedure for the installed database product and version. Do not assume a procedure for one engine applies to another.
Reduce exposure at every network boundary
Compare listening sockets with both the provider firewall and the host firewall. Every reachable listener needs a reason, an owner, and an approved source range. Ubuntu documents UFW as initially disabled; verify the machine’s current state.
Protect administrative access before changing firewall or SSH policy. Keep a tested second session or provider-console path so a mistaken rule does not cause a lockout. This gate is complete only when observed reachability matches the approved design.
PHP’s manual gives a sharper boundary: PHP-FPM must not be reachable from an untrusted network because a FastCGI client can influence request configuration. Limit the FPM endpoint to the intended local or private caller. Apply the same test to the database. Public database access is not a harmless default to tidy later.
Make security updates an operation, not an intention
Check that the Ubuntu release is still in the support window relevant to its enabled repositories. Review recent update activity, pending updates, failed jobs, and whether a reboot is waiting. Assign an owner and a maintenance path for updates that require service restarts or a host reboot.
Ubuntu says unattended-upgrades is included in default Server installations from Ubuntu 18.04 LTS and applies security updates automatically. It also says third-party repositories and PPAs are not covered automatically unless configured. Those are documented installation behaviors, not proof about this machine. Inspect the effective policy and its logs.
Ubuntu also documents AppArmor as installed and loaded by default. Confirm its current status and investigate denials rather than disabling enforcement to make an unexplained failure disappear.
Prove Nginx routes only the intended hosts
Nginx selects a virtual server by listening address and port, then by the request’s Host field. If no name matches, the default server for that address and port handles the request. Test every expected hostname and an unexpected hostname over every exposed address family. An unmatched request must not reveal another application, a setup page, or a directory intended for a different site.
Confirm each document root contains only public assets and the application’s public entry point. Keep diagnostic pages, environment files, database dumps, deployment archives, and private keys outside it.
Before any reload, validate the complete effective configuration. Nginx’s test checks syntax and attempts to open referenced files. A failed reload keeps the old configuration, but that does not protect against a syntactically valid mistake. Preserve the previous reviewed configuration and define a rollback trigger.
Use the Nginx server-block guide for implementation details. Production sign-off needs the routing results, not a screenshot of a configuration file.
Treat TLS renewal as part of the service
Confirm HTTPS serves the expected certificate for every production hostname and that the intended redirect works. Verify the renewal mechanism installed on this host; Certbot says the installation method determines the actual setup.
Run a renewal dry run through the approved, tested procedure and route renewal failure and certificate-expiry alerts to a monitored destination. Record who responds. Do not manually edit Certbot renewal files; its documentation warns that this can break renewal. The Nginx and Certbot guide covers certificate implementation.
Size PHP-FPM from measurements
Identify each application’s pool, operating-system identity, and Nginx endpoint. Separate pools where applications cross a trust boundary. Limit write access to required directories.
PHP-FPM supports static, dynamic, and on-demand process management. Capacity is not a universal number. Measure worker memory, concurrency, and queue behavior; leave headroom for Nginx, the database, the operating system, and bursts; then set a sustainable limit. Recheck after application or extension changes.
PHP states that displayed errors should be disabled in production because output can reveal confidential information, while error logging can preserve diagnostic detail. Verify both the browser response and the log destination. If the FPM status page is enabled, PHP says it must be limited to internal requests or known client addresses because it reveals request URLs and resource information.
Verify OPcache behavior against the deployment method. PHP documents that disabling timestamp validation requires an explicit cache reset or web-server restart before filesystem changes take effect. Do not copy cache sizes or validation settings from another host. Confirm that a release becomes visible when expected and that rollback does too.
Finally, align request and upload limits across Nginx, PHP, and the application. The smallest effective limit wins, and inconsistent settings produce failures that look intermittent to users.
Give the application the smallest database role it needs
Use a dedicated application account rather than an administrative account. MySQL’s privilege guidance says an account should receive only the privileges it needs and calls for particular caution with file and administrative privileges. Review privileges against real application operations, including migrations, instead of granting broad access to avoid a deployment error.
Check both account host scope and the database listener. MySQL documents that bind addresses determine which interfaces accept connections; MariaDB behavior and packaged configuration can differ. If the database is local, prove it is not reachable from an untrusted network. If it is remote, restrict the route and use the installed product’s documented transport protection.
Store credentials outside the public document root, restrict who can read them, and document rotation. Monitor authentication failure, connection exhaustion, storage pressure, and slow queries without exposing query text or secrets.
Make the deployment reversible
Give every release an immutable identifier. Record the artifact, configuration revision, migration state, validation result, and rollback decision. File timestamps are not a release record.
Define which changes can roll back independently and which database changes require a forward fix. Test the rollback path before traffic depends on it. Account for scheduled tasks, background workers, caches, and writable data that a web-only check misses.
Keep ownership and permissions explicit, but do not copy a universal numeric mode. The correct access depends on which identities deploy, serve, and write. The evidence is that each identity can do exactly its job and cannot modify unrelated code, configuration, or secrets.
Monitor the entire request path
An external HTTPS check proves more than a local process check: DNS, routing, Nginx, TLS, and at least one response path must all work. Add application checks that exercise PHP and, where safe, a database read. Keep the health endpoint cheap and prevent it from disclosing versions, configuration, or credentials.
Monitor HTTP error rate, request time, Nginx errors, PHP-FPM queue and capacity signals, database connection pressure, disk space and inodes, memory pressure, backup results, and certificate renewal. Thresholds must come from measured baseline and service requirements, not a generic checklist.
Route every actionable alert to a person or rotation. Record the first diagnostic link and the escalation path. An alert without an owner is a log entry with better typography.
Run one launch rehearsal
Before DNS or traffic changes, rehearse the production path using the real hostname and intended address families. Verify HTTP and HTTPS behavior, static files, a PHP request, a database-backed request, expected not-found and error responses, logging, and any application feature that writes data or schedules work.
Then test service recovery after the kind of restart your maintenance plan permits. Confirm monitoring notices the interruption and recovery. Finish by reviewing the rollback trigger and the evidence table with the person responsible for the launch.
Mark a gate complete only when its evidence exists. “Configured” describes an action. “Observed working, recoverable, and monitored” describes a production system.
Sources
- Ubuntu security updates — automatic-update behavior, repository coverage, and support context; accessed September 3, 2026.
- Ubuntu Server firewall documentation — UFW role and initial state; accessed September 3, 2026.
- Ubuntu Server AppArmor documentation — AppArmor state, modes, and audit handling; accessed September 3, 2026.
- Nginx request processing — virtual-server and default-server selection; accessed September 3, 2026.
- Nginx command-line parameters and Nginx control — configuration validation and reload behavior; accessed September 3, 2026.
- PHP error basics — production error display and logging; accessed September 3, 2026.
- PHP-FPM overview, FPM configuration, and FPM status — network boundary, pools, capacity, and status-page restriction; accessed September 3, 2026.
- PHP OPcache configuration — cache and timestamp behavior; accessed September 3, 2026.
- MySQL security, privilege guidance, and server variables — recovery testing, least privilege, and listener configuration; accessed September 3, 2026.
- Certbot user guide — automated renewal, dry runs, and renewal-file handling; accessed September 3, 2026.
An independent publication. Not affiliated with any prior owner of this domain.