Install a production LEMP stack on Ubuntu
Build nginx, MariaDB, PHP-FPM, TLS, isolated application paths, FastCGI safety rules, health checks, and backups.
Deploy a maintainable Linux, nginx, MariaDB, and PHP application baseline with explicit sockets and no public database listener.
- Ubuntu Server 24.04 LTS
- PHP 8.3
- Supported host and recovery access Use a supported distribution, keep an independent root-capable session, and record the current package repositories before changing the PHP runtime.
cat /etc/os-release && uname -m - Application compatibility Confirm the application, framework, extensions, and deployment tooling explicitly support the target PHP branch.
- Configuration backup Archive the active PHP, web-server, and application configuration before packages, pools, or handlers change.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A production Ubuntu nginx, MariaDB and PHP-FPM application stack with explicit Unix-socket routing.
- An isolated release tree, least-privilege local database account, HTTPS, firewall, logs and verified backup/rollback paths.
- nginx serves only the public directory, passes only valid PHP entry points to the dedicated pool, and cannot expose hidden configuration files.
- The complete request and data path survives controlled restart and produces concrete health, certificate, listener and backup evidence.
Architecture
How the parts fit together
nginx terminates TLS, selects the named server block and serves static assets directly. `try_files` constrains routing, while an exact FastCGI location sends existing PHP entry points to the application's private FPM socket. FPM runs as a non-login identity with bounded workers. MariaDB accepts the application's least-privilege account only locally. Release code, shared writable state, logs and backups have separate ownership and lifecycle.
- Inventory and patch the supported host, then install only signed required packages.
- Create application/database identities and separated code/state directories.
- Validate pool and nginx candidates, then enable the exact hostname and certificate.
- Restart deliberately and verify HTTPS, FastCGI, database, listeners, logs and backup output.
Assumptions
- Ubuntu 24.04 LTS, authoritative DNS, console recovery and a representative application release are available.
- The application has a public front controller, private writable-state list, health endpoint and tested schema migration/rollback procedure.
- Secrets are delivered through protected configuration and neither database nor FPM socket needs public network exposure.
Key concepts
- server block
- nginx configuration selected by address and hostname, defining one site's routing and security boundary.
- try_files
- A filesystem existence/routing guard that prevents arbitrary paths from reaching PHP.
- FastCGI socket
- A private Unix endpoint connecting nginx to one application FPM pool.
- local database principal
- An account whose host and schema grants limit where and what the application may access.
Before you copy
Values used in this guide
{{app}}Short application identifier used in service, pool, directory, and configuration names.
Example: billing{{appGroup}}Operating-system group that owns application-readable or shared runtime paths.
Example: billing{{appUser}}Non-login operating-system identity used by the application workers.
Example: billing{{database}}Application database name; use an identifier approved by the database naming policy.
Example: billing{{dbPassword}}Generated database credential supplied through a protected secret channel, never committed to source control.
Example: use-a-secret-manager-generated-value{{dbUser}}Least-privilege database account used by the application.
Example: billing_app{{domain}}Fully qualified public or internal hostname served by this configuration.
Example: billing.example.com{{managementCidr}}Trusted administration network in CIDR notation.
Example: 192.0.2.0/24{{phpVersion}}Exact PHP major and minor branch used by the target runtime.
Example: 8.3Security and production boundaries
- Never use a broad PHP regex that executes uploaded or nonexistent files; use an exact front controller or existence guard.
- Keep database credentials, TLS private keys and environment files unreadable by nginx and outside the public document root.
- Allow SSH only from the management network and validate that the firewall preserves an independent recovery route.
Stop before continuing if
- Stop if nginx resolves an unexpected socket/root, MariaDB listens publicly, package sources are unapproved, or certificate issuance cannot authenticate the hostname.
- Do not promote if representative PHP requests, writable paths, sessions, database queries, logs or backup restoration evidence are incomplete.
instruction
Patch and inspect the host
Update supported packages, verify time, DNS, and current listeners, and keep a recovery session open.
Why this step matters
Patch and inspect the host is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Update supported packages, verify time, DNS, and current listeners, and keep a recovery session open. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Patch and inspect the host”. The scope must remain limited to the selected application and supported host.
Syntax explained
apt- Invokes the principal tool or configuration operation for “Patch and inspect the host”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
&&- Runs the following operation only when the previous command succeeds, preventing a reload or state transition after a failed validation.
Values stay on this page and are never sent or saved.
sudo apt update && sudo apt upgrade --yes; timedatectl status; getent ahosts {{domain}}; sudo ss -lntupHost state, DNS, and listeners are recorded.
Checkpoint: Checkpoint: Patch and inspect the host
Continue whenHost state, DNS, and listeners are recorded. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when apt reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Patch and inspect the host” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v apt 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
command
Install nginx, MariaDB, and PHP-FPM
Install the Ubuntu-supported runtime and only application-required extensions.
Why this step matters
Install nginx, MariaDB, and PHP-FPM is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Install the Ubuntu-supported runtime and only application-required extensions. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Install nginx, MariaDB, and PHP-FPM”. The scope must remain limited to the selected application and supported host.
Syntax explained
apt- Invokes the principal tool or configuration operation for “Install nginx, MariaDB, and PHP-FPM”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
sudo apt install --yes nginx mariadb-server php-fpm php-cli php-mysql php-opcache php-curl php-mbstring php-xml php-zipnginx, MariaDB, PHP-FPM, and required extensions install.
Checkpoint: Checkpoint: Install nginx, MariaDB, and PHP-FPM
Continue whennginx, MariaDB, PHP-FPM, and required extensions install. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when apt reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Install nginx, MariaDB, and PHP-FPM” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v apt 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
command
Create an isolated application layout
Use a non-login application identity, immutable releases, and a public directory containing no writable uploads.
Why this step matters
Create an isolated application layout is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Use a non-login application identity, immutable releases, and a public directory containing no writable uploads. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Create an isolated application layout”. The scope must remain limited to the selected application and supported host.
Syntax explained
useradd- Invokes the principal tool or configuration operation for “Create an isolated application layout”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
Values stay on this page and are never sent or saved.
sudo useradd --system --home-dir /srv/{{app}} --shell /usr/sbin/nologin {{appUser}}; sudo install -d -o {{appUser}} -g {{appGroup}} -m 0750 /srv/{{app}}/{releases,shared}; sudo install -d -o {{appUser}} -g {{appGroup}} -m 0755 /srv/{{app}}/current/publicThe public root and writable shared storage are separated.
Checkpoint: Checkpoint: Create an isolated application layout
Continue whenThe public root and writable shared storage are separated. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when useradd reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Create an isolated application layout” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v useradd 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
command
Provision a local least-privilege database account
Create one utf8mb4 database and a localhost-only account; deliver the secret through protected deployment configuration.
Why this step matters
Provision a local least-privilege database account is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Create one utf8mb4 database and a localhost-only account; deliver the secret through protected deployment configuration. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Provision a local least-privilege database account”. The scope must remain limited to the selected application and supported host.
Syntax explained
mariadb- Invokes the principal tool or configuration operation for “Provision a local least-privilege database account”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
Values stay on this page and are never sent or saved.
sudo mariadb --protocol=socket -e "CREATE DATABASE \`{{database}}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER '{{dbUser}}'@'localhost' IDENTIFIED BY '{{dbPassword}}'; GRANT ALL PRIVILEGES ON \`{{database}}\`.* TO '{{dbUser}}'@'localhost';"The account is limited to one database and localhost.
Checkpoint: Checkpoint: Provision a local least-privilege database account
Continue whenThe account is limited to one database and localhost. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when mariadb reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Provision a local least-privilege database account” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v mariadb 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
config
Create a dedicated FPM pool
Give the application a unique identity, Unix socket, worker budget, slow log, and private temporary directories.
Why this step matters
Create a dedicated FPM pool is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Give the application a unique identity, Unix socket, worker budget, slow log, and private temporary directories. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Create a dedicated FPM pool”. The scope must remain limited to the selected application and supported host.
Syntax explained
sudoedit- Invokes the principal tool or configuration operation for “Create a dedicated FPM pool”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
/etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf- Is the persistent configuration boundary changed by this step. Keep an attributable backup and validate the consumer before reload.
/etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.confValues stay on this page and are never sent or saved.
sudoedit /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf; sudo php-fpm{{phpVersion}} -ttThe application pool validates and names an explicit socket.
Checkpoint: Checkpoint: Create a dedicated FPM pool
Continue whenThe application pool validates and names an explicit socket. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when sudoedit reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Create a dedicated FPM pool” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v sudoedit 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
config
Create a safe nginx server block
Set server_name and root, use try_files for the front controller, deny hidden files, and pass only existing PHP scripts to the exact pool socket.
Why this step matters
Create a safe nginx server block is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Set server_name and root, use try_files for the front controller, deny hidden files, and pass only existing PHP scripts to the exact pool socket. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Create a safe nginx server block”. The scope must remain limited to the selected application and supported host.
Syntax explained
sudoedit- Invokes the principal tool or configuration operation for “Create a safe nginx server block”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
/etc/nginx/sites-available/{{domain}}- Is the persistent configuration boundary changed by this step. Keep an attributable backup and validate the consumer before reload.
/etc/nginx/sites-available/{{domain}}Values stay on this page and are never sent or saved.
sudoedit /etc/nginx/sites-available/{{domain}}; sudo ln -s /etc/nginx/sites-available/{{domain}} /etc/nginx/sites-enabled/{{domain}}; sudo rm -f /etc/nginx/sites-enabled/default; sudo nginx -tnginx syntax passes and the named host references the application socket.
Checkpoint: Checkpoint: Create a safe nginx server block
Continue whennginx syntax passes and the named host references the application socket. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when sudoedit reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Create a safe nginx server block” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v sudoedit 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
command
Enable the firewall and TLS
Allow only management SSH plus HTTP/HTTPS and use Certbot's nginx integration after DNS validation.
Why this step matters
Enable the firewall and TLS is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Allow only management SSH plus HTTP/HTTPS and use Certbot's nginx integration after DNS validation. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Enable the firewall and TLS”. The scope must remain limited to the selected application and supported host.
Syntax explained
ufw- Invokes the principal tool or configuration operation for “Enable the firewall and TLS”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
Values stay on this page and are never sent or saved.
sudo ufw allow from {{managementCidr}} to any port 22 proto tcp; sudo ufw allow 'Nginx Full'; sudo ufw --force enable; sudo certbot --nginx -d {{domain}} --redirectThe firewall is enabled and HTTPS certificate issuance succeeds.
Checkpoint: Checkpoint: Enable the firewall and TLS
Continue whenThe firewall is enabled and HTTPS certificate issuance succeeds. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when ufw reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Enable the firewall and TLS” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v ufw 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
verification
Restart and verify the complete stack
Validate nginx and FPM, ping MariaDB, restart all services, and inspect recent high-priority logs.
Why this step matters
Restart and verify the complete stack is a separate checkpoint because it establishes one auditable part of deploy a maintainable linux, nginx, mariadb, and php application baseline with explicit sockets and no public database listener. Performing it independently keeps a failed prerequisite, syntax error, or compatibility mismatch from being hidden by later actions.
What to understand
Validate nginx and FPM, ping MariaDB, restart all services, and inspect recent high-priority logs. Read the complete command or configuration before execution, replace the documented placeholders, and compare the target host with the supported environments listed at the beginning of this tutorial.
Capture the before-state and the exact output in the change record. If this step modifies a file or service, validate the candidate with its native checker before any reload; a successful process exit alone does not prove that the application uses the intended value.
Use a representative staging or canary host first. Repeat the stated checkpoint immediately, because proceeding with ambiguous evidence makes the later end-to-end verification and rollback materially harder.
System changes
- This step may affect the files, packages, identities, services, or runtime policy named in “Restart and verify the complete stack”. The scope must remain limited to the selected application and supported host.
Syntax explained
nginx- Invokes the principal tool or configuration operation for “Restart and verify the complete stack”; confirm that it resolves to the expected packaged executable or file before using elevated privileges.
{{…}}- Marks an operator-supplied value. Replace every placeholder deliberately; do not paste untrusted text or leave braces in the production command.
Values stay on this page and are never sent or saved.
sudo nginx -t; sudo php-fpm{{phpVersion}} -tt; sudo mariadb-admin ping; sudo systemctl restart mariadb php{{phpVersion}}-fpm nginx; sudo journalctl -u nginx -u php{{phpVersion}}-fpm -u mariadb -p warning -n 50 --no-pagerAll services are active with no new startup errors.
Checkpoint: Checkpoint: Restart and verify the complete stack
Continue whenAll services are active with no new startup errors. Treat this concrete evidence as the minimum success signal and also confirm that unrelated services and the previous healthy path remain unchanged.
Stop whenStop before the next step when nginx reports an error, the output differs materially from the example, an unexpected package/service/path is affected, or the required before-state and rollback artifact are missing.
If this step fails
The command for “Restart and verify the complete stack” fails, produces no useful evidence, or the expected service/configuration state is absent.
Likely causeA placeholder targets the wrong environment, the installed version uses another path or unit name, permissions are insufficient, or an earlier prerequisite was not satisfied.
command -v nginx 2>/dev/null || truesystemctl --failed --no-pager 2>/dev/null || truejournalctl -p warning -n 50 --no-pager 2>/dev/null || true
ResolutionDo not improvise a privileged workaround. Re-read the environment and variable table, inspect the official source for the installed version, restore the candidate file if necessary, and repeat only this checkpoint.
Security notes
- Keep secrets out of shell history, process arguments, screenshots, logs, and tutorial placeholders. Elevate only the narrow command that requires it and preserve package signature and TLS verification.
Alternatives
- When the host layout or software version differs, use the vendor-supported equivalent in a disposable staging environment and document the mapping before touching production.
Stop conditions
- Stop if the action broadens network exposure, permissions, package sources, writable paths, or service privileges beyond the tutorial's declared architecture.
Finish line
Verification checklist
curl --fail --silent --show-error https://{{domain}}/healthThe health endpoint succeeds through nginx and FPM.MYSQL_PWD='{{dbPassword}}' mariadb -h localhost -u {{dbUser}} {{database}} -e 'SELECT CURRENT_USER();'; sudo ss -lntp | grep ':3306' || trueThe application account connects and MariaDB is absent from public interfaces.Recovery guidance
Common problems and safe checks
The web server returns 502/503 or reports that the FastCGI upstream is unavailable.
Likely causeThe configured socket and the active PHP-FPM pool disagree, the pool failed validation, or Unix socket ownership does not permit the web-server worker to connect.
systemctl status 'php*-fpm' --no-pagerfind /run -maxdepth 3 -type s -name '*php*fpm*' -lsjournalctl -u nginx -u apache2 -u php8.3-fpm -p warning -n 80 --no-pager
ResolutionMatch the virtual host to one validated pool socket and explicit owner/group/mode, then reload only after both native configuration tests pass.
CLI reports the intended PHP version but the HTTP application uses another version or INI value.
Likely causeCLI and FPM are separate SAPIs, the virtual host targets another pool, or a later scan-directory file overrides the reviewed setting.
php --inisystemctl list-units --type=service '*php*fpm*' --no-pagergrep -R -n 'fastcgi_pass\|SetHandler' /etc/nginx /etc/apache2 2>/dev/null
ResolutionTrace the request from hostname through the selected handler and pool to its loaded INI tree; correct that specific boundary rather than changing global CLI alternatives.
Validation passes but a representative request fails after the reload.
Likely causeThe syntax is legal but a path, permission, resource limit, extension, database grant, network dependency, or systemd restriction is incompatible with the real workload.
systemctl --failed --no-pagerjournalctl -p warning --since '-10 min' --no-pagercurl --fail --silent --show-error https://example.com/health
ResolutionRemove the host from traffic, compare the before-state, revert the smallest changed layer, and use the preserved logs plus a safe fixture to refine the candidate in staging.
Recovery
Rollback
Restore the previous nginx site, FPM pool, and application release while preserving database backups.
- Restore the prior server block and pool, validate both services, and reload.
- Switch the release symlink back and run the previous health check.
- Restore database state only from a verified backup if schema changes cannot roll forward.
Evidence