LEMP Stack on a Dedicated Server – Nginx, PHP-FPM and MariaDB

A bare-metal-specific walkthrough that takes you from a freshly provisioned dedicated server to a fully operational LEMP stack — covering root-level process isolation, firewall integration, and production-ready configuration details that shared or VPS environments typically abstract away.
Save This Article
A man pushes a cart with servers in a data center.
At a Glance

Running a LEMP stack on dedicated hardware removes the abstractions that shared and virtual environments impose — but it also transfers full responsibility for process isolation, socket permissions, and network exposure directly to you. Getting those interdependencies right from the first deployment is what separates a stable production system from one that fails under traffic in ways that are difficult to diagnose.

This guide walks you through installing and configuring Nginx, PHP-FPM, and MariaDB on a dedicated server, then shows you how to validate each layer — live PHP execution, log confirmation, and an external port scan — before any traffic reaches the machine.

0 out of 5

Why bare-metal changes every configuration decision you make for LEMP

Save This Article

About the Author

Written by Kristian

Freelance web developer & digital marketer

About the Author

Written by Kristian

Freelance web developer & digital marketer

Table of Contents

A LEMP stack — Linux, Nginx, PHP-FPM, and MariaDB — is one of the most capable web server configurations available on a dedicated server. Unlike shared environments where platform abstractions handle software installation for you, a gives you root access to every layer of the stack.

That control is an advantage, but it also means every configuration decision is yours to make: process isolation, socket communication between PHP-FPM and Nginx, database binding, and firewall rules all require deliberate choices before your first application goes live. The combination of Nginx and PHP-FPM is particularly well suited to dedicated hardware. Nginx handles concurrent connections with a non-blocking event loop, while PHP-FPM manages PHP processes in isolated worker pools.

On a host with no hypervisor overhead and no shared CPU contention, this architecture can sustain high request throughput without contention from other tenants and with direct control over resource allocation. MariaDB completes the stack with a drop-in MySQL-compatible database engine that benefits directly from the dedicated server's guaranteed and storage — especially when paired with storage.

Why a Dedicated Server Changes How You Install a LEMP Stack

On a dedicated server, you have direct, unmediated access to the operating system kernel, the network stack, and every process running on the machine. That fact changes a LEMP stack installation from a guided, platform-assisted procedure into a sequence of deliberate architectural decisions — each with lasting consequences for performance, security, and maintainability.

Each link in that chain must be verified independently before any real traffic touches the server, because no upstream layer will catch a misconfiguration on your behalf.

On a shared or platform, the hosting layer makes many of those decisions invisibly. PHP runs under a preconfigured module, database bindings are often preset, and kernel parameters are locked by the hypervisor or restricted by the — parameters such as fs.file-max, which governs the open-file ceiling that Nginx worker connections depend on directly. On bare metal, none of those defaults exist and none of those limits are imposed externally.

You decide whether PHP-FPM communicates with Nginx over a TCP port or a Unix socket, and that choice affects both request latency and file-system permission boundaries in ways a VPS control panel would otherwise abstract away. You choose which network interface MariaDB binds to and whether its port is reachable from outside the machine at all — a firewall rule you write and own entirely, with no shared-tenant policy sitting above it.

Process isolation between services is where bare-metal ownership has the most concrete security consequence. On dedicated hardware, you can assign Nginx, PHP-FPM, and MariaDB each to a distinct system user with tightly scoped file permissions. This is not a theoretical hardening exercise: if a PHP vulnerability is exploited, a correctly isolated PHP-FPM pool is less able to reach other apps’ files or Nginx configuration — but it can usually still read its own database credentials, because the application needs them.

That boundary is only as strong as the user and group assignments you configure during initial setup, and a dedicated server gives you the kernel-level tools to enforce it without negotiating with a shared environment that does not.

A person is working on a cable cabinet with many cables.

Establishing a clean security baseline and resolving leftover provisioning conflicts before installing any stack component prevents subtle dependency failures that are notoriously difficult to trace once Nginx or MariaDB is already running.

How to Prepare Your Server Before Touching the LEMP Stack

Examples below assume Ubuntu 24.04 LTS. On a dedicated server, preparation establishes the security and dependency baseline every later component inherits. Remove leftover Apache/PHP packages from a failed prior provisioning attempt before you continue.

  • Update the package index and apply pending security patches:

    bash
    sudo apt update && sudo apt upgrade -y
  • Set the hostname so logs and certificates match identity:

    bash
    sudo hostnamectl set-hostname your-host.example
  • Enable a minimal firewall before exposing daemons:

    bash
    sudo ufw default deny incoming && sudo ufw allow OpenSSH && sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw enable
  • Confirm the Ubuntu release:

    bash
    lsb_release -a
  • Verify no conflicting web server is installed:

    bash
    dpkg -l | grep -E 'apache2|nginx'

Then install the stack packages, for example:

bash
sudo apt install -y nginx php-fpm php-mysql mariadb-server

On Ubuntu 24.04, enable the stack services:

bash
sudo systemctl list-unit-files 'php*-fpm.service'
sudo systemctl enable --now nginx php8.3-fpm mariadb

With packages installed and the firewall baseline in place, move on to Nginx, PHP-FPM, and MariaDB configuration.

How to Install and Configure Nginx on a Dedicated Server

Installing Nginx on a dedicated server is a single-command operation, but the configuration work that follows is where bare-metal ownership becomes meaningful. Once the package is installed and enabled as a systemd service, update the UFW ruleset to permit traffic on ports 80 and 443 exclusively — confirming the HTTP and HTTPS allowances that the preparation stage already opened for Nginx.

Sizing Nginx workers for the available hardware prevents idle capacity under load.

A server with eight idle cores costs the same as one running at full capacity — misconfigured workers make that difference. The practical decision rule is: start from worker_processes auto; (or a core-count baseline), then size worker_connections against the open-file ceiling you configured via worker_rlimit_nofile / systemd LimitNOFILE — not against an arbitrary default. Optimal worker count also depends on disks and load profile.

The default Nginx configuration is sized for a generic environment, not for the hardware you are actually running. Two values require adjustment before the server handles any real traffic. The first is the worker process count: prefer worker_processes auto; as a starting point, then tune if your disks or traffic profile need a different value. Leaving an outdated default of one wastes cores under load.

A higher core count and generous RAM allocation — both realistic on dedicated hardware — allow this value to be set significantly higher than the defaults designed for shared or virtualized environments.

After adjusting both values, test the configuration file before reloading the service. Nginx includes a built-in syntax check for this purpose. If the new configuration fails the syntax check, Nginx keeps serving with the previous good config. After a successful reload, old workers finish existing connections while new workers take new ones — so always run nginx -t first. Run the following command before continuing:

bash
sudo nginx -t && sudo systemctl reload nginx

Once the reload succeeds, confirm the service status and check that Nginx is listening on the expected ports using a network socket query.

This verification step closes the installation phase cleanly and establishes a known-good baseline before PHP-FPM is introduced. Teams working from a dedicated server with full root access can apply every one of these tuning decisions without restriction — something a shared or abstracted environment rarely permits.

A desk with a coffee cup, plants, and note-taking materials.

Keeping PHP execution in a separate process pool from Nginx means that a spike in application load or a PHP crash cannot directly destabilize the web server handling incoming connections.

How to Install PHP-FPM and Isolate It from the Web Processes

PHP-FPM is the process manager that handles PHP execution separately from Nginx, and on a dedicated server the isolation between these two processes is both achievable and necessary. On Ubuntu, installing the package alongside the correct PHP version adds the FPM service alongside the interpreter. Enable it as a systemd service immediately so it persists across reboots, just as you did with Nginx.

The critical configuration step is pool process isolation: PHP-FPM runs pools, and each pool operates under a defined system user and group. By default, the pool may share the same user identity as Nginx. Change user, group, listen.owner, listen.group, and listen.mode in the pool file. Create a dedicated system user — one with no login shell and no home directory — and assign it exclusively to the PHP-FPM pool configuration file.

ini
; /etc/php/8.3/fpm/pool.d/app.conf (Ubuntu 24.04 example)
user = appuser
group = appuser
listen = /run/php/php8.3-fpm-app.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
nginx
# matching Nginx location
location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php8.3-fpm-app.sock;
}

The pool file defines which user runs the PHP workers, how many child processes are spawned, and how those workers communicate with Nginx. With a dedicated system user in place, a compromised PHP process should not be able to write to files owned by the web server process, and vice versa, unless you explicitly permit that through group ownership. This boundary matters far more on a bare-metal host, where no hypervisor or container runtime provides an additional containment layer.

The next decision is whether Nginx should connect to PHP-FPM through a Unix socket or a local TCP port. On a dedicated server, both processes often run on the same machine, which means localhost loopback latency is negligible. A Unix socket is usually a strong default here: it bypasses the network stack, reduces overhead, and is accessible only to processes with the appropriate filesystem permissions — adding another layer of access control at no cost.

Configure the Nginx fastcgi_pass directive to reference the socket path rather than a loopback address and port. After editing both the pool file and the Nginx server block, reload both services and confirm that PHP-FPM workers are running under the correct user identity.

How to Install and Secure MariaDB on Bare Metal

Installing MariaDB on a dedicated server takes seconds; securing it correctly takes deliberate configuration that most quick-start guides skip. On Ubuntu, the package installs the database daemon and starts it immediately. The first action after installation is not creating a database — it is running sudo mariadb-secure-installation. Prompts and authentication defaults can differ by MariaDB package version.

Run sudo mariadb-secure-installation and review each prompt. Depending on the package version and authentication defaults, anonymous accounts, remote root access, or a test database may already be absent. Remaining defaults are a known attack surface on a bare-metal host with a public IP address, where there is no hypervisor or shared-tenant firewall absorbing inbound probes.

After the security script completes, create an application-specific database user with the narrowest privilege set the application actually requires. A web application that reads and writes its own tables does not need global administrative rights. Grant that user privileges only on its own database, and do so from localhost only.

This scoping means that even if an attacker gains application-level code execution — through a vulnerable plugin or unpatched dependency — the database credentials in that application's configuration file cannot be used to access other databases or issue administrative commands. The principle is least privilege, applied at the database layer.

The final hardening step is daemon network binding. Do not assume MariaDB is wide-open: check with ss -lntp | grep 3306 and the configured bind-address. Many Ubuntu packages already use loopback; if listening more broadly, set bind-address to loopback so the daemon accepts connections only from processes running on the same machine.

For teams managing multiple services across a single bare-metal host, the dedicated server gives you the full system access needed to enforce every one of these controls without platform restrictions.

A man is working on two monitors displaying code.

Matching the Unix socket path precisely between the PHP-FPM pool configuration and the Nginx server block is the single most critical detail that determines whether dynamic requests are passed correctly or silently dropped.

How to Connect Nginx, PHP-FPM, and MariaDB into a Working Stack

The practical consequence of that rule is a strict ordering: confirm the Unix socket path in the PHP-FPM pool file first, then mirror that exact path in the Nginx server block — because a mismatch in either direction fails silently from the browser's perspective, producing a blank response or an unexpected file-download prompt rather than an error you can immediately trace.

A socket path mismatch between PHP-FPM and Nginx produces no useful browser error, so the log file must be your first stop.

The fastcgi_param SCRIPT_FILENAME directive is the most common point of failure at this join: if it resolves to the wrong absolute path, PHP-FPM receives the request but cannot locate the file to execute. On a dedicated server there is no platform layer to reconcile that mismatch; /var/log/nginx/error.log is your first diagnostic stop. Once the server block is edited, validate with sudo nginx -t, then apply with sudo systemctl reload nginx rather than a full restart so active connections are not dropped. On a VPS with a managed control panel, this socket path is often pre-wired and hidden from you; on a dedicated server, you set it explicitly in the pool file and must match it exactly in the server block — there is no platform layer to reconcile a mismatch silently.

The verification sequence matters as much as the configuration itself. Prefer a localhost or source-IP-restricted request (or a CLI check such as curl --resolve / php-cli) over a publicly reachable probe. A temporary PHP info file may confirm Nginx→PHP-FPM, and a brief script that opens a MariaDB connection confirms the full chain — but restrict access and remove both files immediately after verification. Leaving a PHP info file on a public-facing server exposes the complete runtime configuration, PHP version, loaded modules, and environment variables to any visitor, which is an unacceptable exposure on a production host where you hold direct root access and no upstream provider is filtering that path for you.

How to Harden and Tune the LEMP Stack for Production Traffic

Production readiness on a bare-metal LEMP stack means closing the gap between a working installation and one that can withstand real traffic, hostile probes, and sustained load.

The decisions that follow — which headers to suppress, how to size PHP-FPM worker pools against available RAM, and which UFW rules to enforce — determine whether that surface area shrinks to an acceptable level or remains a liability under production conditions.

By default, Nginx includes its version number in HTTP response headers and error pages. Disabling this takes a single directive in the main configuration block, but its effect is meaningful: automated scanners use version strings to target known vulnerabilities, so removing the header reduces noise before any other hardening measure is in place. Similarly, PHP-FPM should not expose its version through response headers. A single configuration line suppresses this.

Neither change affects performance; both reduce the attack surface immediately. PHP-FPM pool limits deserve careful attention before traffic arrives. Set it too high and the server exhausts available RAM under load, triggering swap usage that degrades response times sharply.

Set it too low and legitimate requests queue behind one another. The correct value depends on the average memory footprint of a single PHP process under your application — measure this with a running process list during a representative load period, then divide available RAM by that figure, leaving a meaningful reserve for the operating system and MariaDB. Enabling the MariaDB slow query log gives you a direct view into queries that consume disproportionate time.

Set a threshold of one second as a starting point and evaluate the log after the first full day of production traffic.

A man stands in front of a server cage holding a tablet.

Serving a PHP script that performs an actual MariaDB query through Nginx is the only test that simultaneously confirms socket communication, database authentication, and request routing are all functioning correctly under real conditions.

How to Validate the Full Stack with a Live Application Test

Validation means deploying a minimal PHP script that queries MariaDB and returns a database-sourced response through Nginx — if that single request completes successfully, every link in the chain is confirmed working under real conditions. A static file test or a PHP info page is not sufficient: neither touches the database layer, so neither proves the full stack is wired correctly.

Restrict the test to localhost or a trusted source IP, prefer a CLI check when possible, and remove the file immediately after validation. An obscure path is not access control. Send a request to that path and watch two log files simultaneously: the Nginx access log should record a 200 status code, and the Nginx error log should remain silent.

Common causes of a 502 at this point include a stopped PHP-FPM pool, socket path or permission mismatches, timeouts, or a wrong upstream/fastcgi configuration. A 500 response indicates a PHP execution error, which the PHP-FPM log will detail.

After confirming a successful response, check the PHP-FPM pool status endpoint if you enabled it during configuration. It shows active processes, queued requests, and the maximum children reached since the pool started — figures that reveal whether your pm.max_children value is already being tested. A non-zero queue count during a single manual request is a signal to revisit pool sizing before production traffic arrives.

Finally, run a port scan against the server's public IP from an external machine. Only explicitly required ports should respond—for example, 80 and 443 plus the restricted SSH administration port. MariaDB port 3306 must not be publicly reachable. This external check is the authoritative confirmation that your firewall rules are enforced at the network boundary, not merely assumed from configuration files.

LEMP Stack Components: Role and Configuration Characteristics

CriterionNginxPHP-FPMMariaDB
Primary RoleHandles HTTP connections via non-blocking event loopManages PHP execution in isolated worker poolsStores and serves application data, MySQL-compatible
Process ModelEvent-driven, single process handles many concurrent connectionsPrefork worker pools, each pool runs under own userMulti-threaded, benefits directly from dedicated RAM allocation
Communication MethodAccepts incoming HTTP requests, forwards PHP to PHP-FPMCommunicates with Nginx via Unix socket or TCP portListens on configurable network interface and port binding
Isolation MechanismRuns as its own system user with scoped file permissionsPool-level user assignment limits credential and file accessNetwork binding controls whether port is externally reachable
Key Configuration ConcernOpen-file limits affect maximum simultaneous worker connectionsSocket path choice affects latency and permission boundariesInterface binding must be deliberate before any network exposure

Conclusion – Your LEMP Stack Is Ready for Production

For provider fit and procurement context, see our guide to choosing a dedicated server provider and the honest recommendation overview.

With Nginx serving requests, PHP-FPM isolating worker processes under a dedicated system user, and MariaDB bound exclusively to localhost, your dedicated server is running a stack that shared hosting rarely exposes at this level of control. A full-root VPS can still host a nearly identical LEMP layout. Root-level access to UFW rules, pool configuration files, and the MariaDB bind-address directive means every security boundary is yours to enforce — and yours to verify.

Every security boundary on a dedicated server is yours to enforce directly — no platform abstraction stands between you and the controls.

Pool sizing, socket permissions, and firewall scope require revisiting only when hardware specifications or traffic patterns change materially. Until then, the configuration you have applied is stable by design. Run each component’s status check independently before accepting live traffic, and treat that verification sequence as a standing discipline rather than a one-time step.

FAQ - Frequently Asked Questions

On a bare-metal server, no hypervisor or control panel presets your PHP configuration, database bindings, or kernel parameters — every architectural decision falls to you. That direct, unmediated access to the OS kernel and network stack means choices like socket paths, open-file limits, and MariaDB’s bound network interface all carry lasting consequences for performance and security. Shared and VPS environments hide these steps behind platform abstractions; a dedicated server exposes them fully.
On a dedicated server, you can assign each service its own dedicated Linux system user with tightly scoped file permissions, creating hard boundaries between processes that shared environments typically cannot enforce. This means a compromise of the PHP-FPM worker pool does not automatically grant access to the MariaDB data directory or the Nginx configuration. That level of isolation is a direct consequence of root ownership over every layer of the stack.
On bare-metal hardware, an Unix socket is generally preferred because it avoids the TCP stack entirely, reducing latency and keeping inter-process communication within the kernel’s file-system permission model. The socket path you choose also defines the file-system boundary between the Nginx and PHP-FPM system users, which directly affects your permission hardening. A TCP port is a valid alternative when you need to run PHP-FPM on a separate host, but on a single dedicated server it adds overhead without benefit.
MariaDB should bind exclusively to the loopback interface or a private internal interface unless a remote application host explicitly requires external access. On a dedicated server you control the network stack directly, so there is no platform layer to block external database exposure on your behalf — that firewall rule is your responsibility to set. Leaving MariaDB bound to a public interface without a corresponding firewall restriction is one of the most common bare-metal misconfigurations.
Nginx’s ability to hold simultaneous connections is bounded by the operating system’s per-process open-file limit, which on a bare-metal server you can tune directly in the kernel and systemd unit files. On some managed or restricted VPS offerings, open-file limits may be capped below what a busy reverse proxy needs — that is offer-specific, not a universal VPS rule. Raising the limit on dedicated hardware is a deliberate step that must match your Nginx worker_connections directive to have any effect.
At minimum, you must restrict inbound access to ports 80 and 443 for web traffic, block external access to MariaDB’s port 3306, and ensure your SSH management port is limited to trusted source addresses. On bare metal, no upstream security layer enforces these rules for you, so they must be set explicitly before the first application goes live. A practical, command-level walkthrough of UFW and iptables rule order on Ubuntu is a separate discipline covered in dedicated firewall guides.
Nginx’s non-blocking event loop and PHP-FPM’s isolated worker pools can sustain high request throughput without the CPU contention and hypervisor overhead that cap performance in virtualised environments. On a dedicated server, there is no noisy-neighbor effect stealing CPU cycles or memory bandwidth mid-request. MariaDB also benefits directly from guaranteed RAM and storage I/O — particularly when the server is equipped with NVMe drives — because no other tenant competes for those resources.
Because bare-metal ownership gives you unrestricted control over the operating system, you can run Nginx, PHP-FPM, and MariaDB under separate system users, each with tightly scoped file permissions that prevent one compromised service from accessing another’s files or processes. This level of isolation is rarely achievable on shared or VPS platforms, where the hosting layer or hypervisor constrains how system users and permissions are structured. On a dedicated server, configuring these boundaries deliberately before your application goes live is a foundational security step rather than an optional refinement.

Share this article

Save This Article
Kristian

About the Author

Kristian is a freelance web developer with years of hands-on experience building and hosting websites for real-world projects. On this site, he shares practical insights on dedicated server infrastructure and hosting to help readers choose the right setup for their needs.

Was This Article Helpful?

Your feedback helps us improve the quality, relevance, and usefulness of the content we publish.
0 out of 5 (0 ratings)

About This Article

Editorial Note
Affiliate Link Disclosure *
Report an Error

You May Also Like

This website uses cookies

We use cookies to personalize content, provide social media features, and analyze our traffic. We also share information about your use of our site with our analytics partners. You can change your preferences at any time. For more information, please see our Privacy Policy.