OneLinersCommand workbench
Guides
Services & Applications / Databases & Data

Install a production LEMP stack on Ubuntu

Build nginx, MariaDB, PHP-FPM, TLS, isolated application paths, FastCGI safety rules, health checks, and backups.

90 min8 stepsChanges system stateRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 8 steps completed
Goal

Deploy a maintainable Linux, nginx, MariaDB, and PHP application baseline with explicit sockets and no public database listener.

Supported environments
  • Ubuntu Server 24.04 LTS
  • PHP 8.3
Prerequisites
  • 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.
Operating boundary

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

System
  • 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.
Observable outcome
  • 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.

nginx server blockControls hostname, TLS, public root, static files, FastCGI parameters and access/error logs.
Dedicated FPM poolIsolates PHP execution, socket permissions and worker capacity.
Local MariaDBProvides one application database without a public listener.
UFW and TLSLimit ingress and establish authenticated encrypted client connections.
  1. Inventory and patch the supported host, then install only signed required packages.
  2. Create application/database identities and separated code/state directories.
  3. Validate pool and nginx candidates, then enable the exact hostname and certificate.
  4. 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.3

Security 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.
01

instruction

Patch and inspect the host

caution

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.
Command
Fill variables0/1 ready

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 -lntup
Example output / evidence
Host 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.

Safe checks
  • command -v apt 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
02

command

Install nginx, MariaDB, and PHP-FPM

caution

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.
Command
sudo apt install --yes nginx mariadb-server php-fpm php-cli php-mysql php-opcache php-curl php-mbstring php-xml php-zip
Example output / evidence
nginx, 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.

Safe checks
  • command -v apt 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
03

command

Create an isolated application layout

caution

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.
Command
Fill variables0/3 ready

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/public
Example output / evidence
The 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.

Safe checks
  • command -v useradd 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
04

command

Provision a local least-privilege database account

caution

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.
Command
Fill variables0/3 ready

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';"
Example output / evidence
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.

Safe checks
  • command -v mariadb 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
05

config

Create a dedicated FPM pool

caution

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.
File /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudoedit /etc/php/{{phpVersion}}/fpm/pool.d/{{app}}.conf; sudo php-fpm{{phpVersion}} -tt
Example output / evidence
The 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.

Safe checks
  • command -v sudoedit 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
06

config

Create a safe nginx server block

caution

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.
File /etc/nginx/sites-available/{{domain}}
Configuration
Fill variables0/1 ready

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 -t
Example output / evidence
nginx 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.

Safe checks
  • command -v sudoedit 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
07

command

Enable the firewall and TLS

caution

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.
Command
Fill variables0/2 ready

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}} --redirect
Example output / evidence
The 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.

Safe checks
  • command -v ufw 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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.
08

verification

Restart and verify the complete stack

caution

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.
Command
Fill variables0/1 ready

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-pager
Example output / evidence
All 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.

Safe checks
  • command -v nginx 2>/dev/null || true
  • systemctl --failed --no-pager 2>/dev/null || true
  • journalctl -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

HTTPS applicationcurl --fail --silent --show-error https://{{domain}}/healthThe health endpoint succeeds through nginx and FPM.
Local database and no public listenerMYSQL_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.

Safe checks
  • systemctl status 'php*-fpm' --no-pager
  • find /run -maxdepth 3 -type s -name '*php*fpm*' -ls
  • journalctl -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.

Safe checks
  • php --ini
  • systemctl list-units --type=service '*php*fpm*' --no-pager
  • grep -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.

Safe checks
  • systemctl --failed --no-pager
  • journalctl -p warning --since '-10 min' --no-pager
  • curl --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.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use Apache with proxy_fcgi when .htaccess compatibility or existing Apache operations are required.
  • Use immutable images or separate database hosts when their patching, private network, backup and rollback controls are stronger.

Operate it safely

  • Add request-rate, upstream-latency, FPM saturation, MariaDB health and certificate-expiry monitoring.
  • Automate atomic releases and recurring restore tests while keeping application migration rollback explicit.

Recovery

Rollback

Restore the previous nginx site, FPM pool, and application release while preserving database backups.

  1. Restore the prior server block and pool, validate both services, and reload.
  2. Switch the release symlink back and run the previous health check.
  3. Restore database state only from a verified backup if schema changes cannot roll forward.

Evidence

Sources and review

Verified 2026-07-25Review due 2026-10-23
Ubuntu Server: install PHPofficialnginx FastCGI moduleofficialMariaDB Server installation guideofficialPHP-FPM installation and configurationofficial