Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

The safest pattern for independent WordPress sites on one LEMP VPS is one Unix account, document root, Nginx server block, PHP-FPM pool and local socket, database, and database user per domain. Correct ownership and permissions keep a compromised site from casually reading another site’s files, while separate databases limit credential and data exposure.

This guide shows a repeatable setup for Debian- or Ubuntu-style systems, explains the permission and socket details that are easy to get wrong, and provides verification and recovery checks. The commands are examples; adapt package names and PHP-version paths to your distribution.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Architecture that keeps sites separate

Map every site to its own operating-system and application resources. A request for example.com should never depend on a shared document root, shared PHP worker identity, or shared database password.

Layer Per-site resource Purpose
Unix identity site1, site2 (non-login service accounts) PHP processes and file operations run with only that site’s permissions.
Files /home/site1/public/, /home/site2/public/ Each installation has a separate document-root tree.
Nginx One server {} block per domain Routes the hostname to the correct root and PHP socket.
PHP-FPM One named pool and Unix socket per site Workers use the site’s UID/GID instead of a common PHP user.
Database One database and least-privilege database user per site A leaked credential grants access only to that site’s tables.
Logs and backups Separate paths and backup sets Investigate incidents and restore one site without mixing data.

A practical layout is:

/home/site1/public/
/home/site2/public/
/run/php/site1.sock
/run/php/site2.sock
/etc/php/<version>/fpm/pool.d/site1.conf
/etc/php/<version>/fpm/pool.d/site2.conf
/etc/nginx/sites-available/site1.conf
/etc/nginx/sites-available/site2.conf

The WordPress Nginx handbook documents the per-site root and server-block pattern. PHP’s pool model supports different UIDs, GIDs, sockets and per-pool limits, which is what makes the process boundary useful.

Plan the server before deploying

Confirm DNS and resource headroom

Create an A or AAAA record for every domain and verify that each name resolves to the VPS before troubleshooting Nginx. Leave capacity for the operating system, database server and all PHP pools; eight workers per pool is only an example, not a safe universal value. RAM usage, PHP version, traffic and plugin workload determine the appropriate pool limits.

Choose independent installations deliberately

Use separate WordPress installations when sites belong to different customers, require different plugins or themes, follow different PHP or update schedules, or need stronger filesystem and database boundaries. WordPress Multisite shares core files and central administration; it is useful when one organization wants a network of sites managed together, but it is not a substitute for per-customer isolation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install only required services

  • Nginx
  • A supported PHP version with PHP-FPM and the extensions required by your themes and plugins
  • A MySQL-compatible database server
  • DNS management and an ACME client supplied by your distribution for HTTPS
  • Optional ACL tooling (setfacl) if you want precise web-server traversal and read permissions

Build each site in a repeatable sequence

  1. Point the domain at the VPS

    Publish DNS records for example.com and www.example.com (and the equivalent names for every additional site). Wait for the records to resolve from outside the server. Do not enable a catch-all virtual host that could send an unknown hostname to the wrong installation.

  2. Create a non-login Unix account and directory tree

    On a Debian- or Ubuntu-style host, create one system account per site:

    sudo useradd --system --home /home/site1 --shell /usr/sbin/nologin site1
    sudo mkdir -p /home/site1/public /home/site1/logs
    sudo chown -R site1:site1 /home/site1
    sudo chmod 750 /home/site1

    Repeat with site2 and its home directory. The account should not have an interactive shell or sudo rights. Keep each site’s files outside directories readable by another site account.

    Nginx, commonly running as www-data, needs only traversal of the path and read access to static files. If ACLs are available, grant those rights without granting write access:

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
    sudo setfacl -m u:www-data:--x /home/site1
    sudo setfacl -R -m u:www-data:rX /home/site1/public

    Alternatively, use a dedicated group and group-read permissions, but do not put all site accounts in one shared writable group. Avoid chmod 777. Make only deliberately writable paths, such as wp-content/uploads, writable by the PHP process.

  3. Install WordPress files with ownership in mind

    Obtain the package from WordPress.org, extract it into /home/site1/public, and set ownership to site1:site1. Keep normal directories non-writable by unrelated users and restrict wp-config.php after configuration:

    sudo chown -R site1:site1 /home/site1/public
    sudo find /home/site1/public -type d -exec chmod 750 {} ;
    sudo find /home/site1/public -type f -exec chmod 640 {} ;
    sudo chmod 640 /home/site1/public/wp-config.php

    WordPress automatic file operations run as the owner of the files. Therefore, your update workflow and ownership must agree: either let the site-owned PHP process perform updates, or run command-line maintenance as site1. Do not make the entire tree world-writable merely to avoid an update prompt.

  4. Create a PHP-FPM pool and private socket

    Create /etc/php/<version>/fpm/pool.d/site1.conf:

    [site1]
    user = site1
    group = site1
    listen = /run/php/site1.sock
    listen.owner = www-data
    listen.group = www-data
    listen.mode = 0660
    pm = ondemand
    pm.max_children = 8

    The pool name, Unix user and group, and socket path must be unique. listen.mode = 0660 lets Nginx connect through the socket’s owner/group without exposing a network listener. PHP documents that an exposed FastCGI endpoint can permit arbitrary code execution; keep FPM on a local Unix socket and never publish it to the Internet.

    What’s actually slowing this PC down?

    Pick the symptom - the matching free tool is one click away.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

    pm.max_children = 8 is a starting example from a deployment pattern, not a recommendation for every VPS. Size it after measuring memory per PHP worker and leaving headroom for the database and other pools. Use separate pool settings for slow sites rather than allowing one workload to consume all workers.

    Duplicate the file for site2, changing the pool name, UID/GID and socket. Reload PHP-FPM only after checking the configuration with your distribution’s service command.

  5. Create a database and database user for the site

    Log in to the database server as an administrator and create credentials that are never reused by another site:

    CREATE DATABASE site1_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'site1_db_user'@'localhost' IDENTIFIED BY 'use-a-long-random-secret';
    GRANT ALL PRIVILEGES ON site1_db.* TO 'site1_db_user'@'localhost';
    FLUSH PRIVILEGES;

    Run the equivalent statements for site2 with different names and a different password. Grant privileges on the site’s database only; do not grant global privileges or use one administrator account in multiple wp-config.php files. WordPress hardening guidance recommends separate databases and users when several blogs share a server.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  6. Configure wp-config.php

    Copy the sample configuration, set the database name, user, password and host, and generate unique authentication salts for each installation. A minimal database section looks like:

    define( 'DB_NAME', 'site1_db' );
    define( 'DB_USER', 'site1_db_user' );
    define( 'DB_PASSWORD', 'use-a-long-random-secret' );
    define( 'DB_HOST', 'localhost' );

    Use a distinct table prefix for each installation even though the databases are already separate; it provides an additional safeguard if a backup is accidentally imported into the wrong database. Keep the file readable by the site account and by the PHP process running under that account, but not writable by Nginx.

  7. Create one Nginx server block per domain

    Save this as /etc/nginx/sites-available/site1.conf:

    server {
        listen 80;
        server_name example.com www.example.com;
        root /home/site1/public;
        index index.php;
    
        location / {
            try_files $uri $uri/ /index.php?$args;
        }
    
        location ~ .php$ {
            try_files $uri =404;
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/site1.sock;
        }
    
        location ~* /.ht {
            deny all;
        }
    }

    For the second site, change server_name, root and fastcgi_pass. Keep common security rules in reviewed include files where practical, but inspect every copied rule: a typo in a location block can expose a configuration or incorrectly route PHP.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

    Enable each site using your distribution’s sites-enabled convention, test the complete configuration, and reload only after the test succeeds:

    sudo nginx -t
    sudo systemctl reload nginx
  8. Start or reload PHP-FPM and Nginx

    Reload the PHP-FPM service for the installed version, then reload Nginx. Check that each socket exists and has the expected owner, group and mode:

    ls -l /run/php/site1.sock /run/php/site2.sock
    getfacl /home/site1/public

    A missing socket usually means the pool failed to parse or the service was not reloaded. A permission-denied error usually means the socket group or directory traversal permissions do not allow Nginx to connect.

  9. Complete HTTPS configuration

    Use the ACME client supported by your distribution to issue certificates for every hostname, then configure Nginx to redirect HTTP to HTTPS. Test both the certificate and renewal process; an expired certificate can take every site offline even when the individual WordPress installations are healthy. HTTPS protects administrator logins, cookies and visitor traffic and should be treated as an operational requirement.

    Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  10. Finish the WordPress installer and verify the site

    Open each HTTPS domain and complete the installer with that site’s database credentials. Then test:

    • /wp-admin login and logout
    • A pretty permalink after saving the intended permalink structure
    • An image upload into wp-content/uploads
    • A plugin or core update using the planned ownership workflow
    • A scheduled task, either through WordPress cron or your chosen system scheduler
    • That the site logs contain the request and the other site’s logs do not

    From a browser or command-line HTTP client, request both domains and confirm that each returns the correct title, files and cookies. Never infer correct isolation from a successful front page alone.

  11. Configure backups, monitoring and incident procedures

    Back up each database and document root separately to storage outside the VPS. Retain enough history to recover from delayed discovery of a compromised plugin. Monitor disk space, PHP-FPM pool saturation, Nginx errors, certificate renewal and backup success. Write down how to disable one site, rotate its database credentials, preserve logs and restore it without taking unrelated sites offline.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Permissions that prevent cross-site file reads

Isolation depends on the complete path, not just the final file mode. A site account must not be able to traverse another site’s home directory, and Nginx must receive only the read and traversal rights needed to serve public files. Keep private material such as backups, deployment keys and configuration exports outside every document root.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Owner: the site’s Unix account.
  • Group: a narrowly scoped group, not a group containing every site account.
  • Directories: executable for the owner and the explicitly authorized web-server identity; writable only where required.
  • Files: readable by the owner and the web-server identity when Nginx must serve them; generally not writable by Nginx.
  • wp-content/uploads: writable by the site’s PHP process, with execution of uploaded scripts blocked by Nginx rules appropriate to your deployment.
  • wp-config.php: not writable by Nginx and not readable by other site accounts.

Test permissions as the actual identities rather than as root. For example, use sudo -u site1 to verify that site1 can update its own files, and use sudo -u site1 with a read attempt against site2’s document root to confirm it is denied. Root can bypass these checks, so a root test does not prove tenant isolation.

How to diagnose common failures

Symptom Likely cause Checks and correction
502 Bad Gateway Socket missing, wrong path, or Nginx cannot connect Inspect the PHP-FPM pool log, verify the socket path, and check socket owner/group/mode and directory traversal.
403 Forbidden for static files Nginx lacks execute permission on a parent directory or read permission on files Review every directory in /home/site1/public; grant only the required ACL or group-read permission.
WordPress asks for FTP credentials during updates Files are owned by a different identity than the PHP worker Align ownership with the site1 pool or run the update as the owner; do not use 777 as a workaround.
Wrong site appears for a hostname Duplicate or missing server_name, DNS pointing elsewhere, or an unintended default server Run nginx -t, inspect enabled blocks, test the Host header and confirm DNS answers.
Database connection error Wrong credentials, host, database name or grants Test the site’s account against only its database and compare the values in wp-config.php.
One site consumes all CPU or memory Unbounded or oversized PHP-FPM pools, a plugin loop or traffic spike Review per-pool worker counts and logs, lower limits carefully, and investigate the offending workload before increasing VPS size.
Uploads fail but pages work wp-content/uploads is not writable by the site account or PHP pool Create the directory with site ownership and grant write permission only there.

Independent installs versus WordPress Multisite

Multisite hosts several sites from one WordPress installation and shares core files and network administration. It can simplify centralized updates and governance when one organization controls every site. The network configuration must be planned at creation time; Nginx generally needs the appropriate network rewrite rules from the beginning.

Choose independent installs when customers must not share an administrative surface, when one site needs a different plugin or theme lifecycle, when PHP versions may diverge, or when a stronger filesystem and database boundary is a requirement. Separate installs cost more to update and monitor, but they make ownership, credentials, backups and incident response explicit.

Understand what this isolation does not protect

Separate accounts and pools reduce the blast radius of ordinary file-permission mistakes and many WordPress-level compromises. They are not a complete security boundary. All sites still share the Linux kernel, the Nginx master process, the database service, the VPS administrator account, hardware and other host resources. A kernel vulnerability, host compromise, malicious administrator or vulnerable shared component can cross those boundaries.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Treat a vulnerable plugin or theme as a possible host-level incident: preserve logs, disable the affected site or pool, rotate its database and WordPress secrets, inspect neighboring sites, patch the vulnerable component and restore from a known-clean backup. Keep the operating system, PHP, Nginx, database server, WordPress core, themes and plugins current. Review writable directories after every deployment and test full restores rather than assuming that a successful backup job is recoverable.

Per-site maintenance checklist

  • Confirm DNS, HTTPS renewal and HTTP-to-HTTPS redirects for every domain.
  • Check that each Nginx block still points to the intended root and PHP socket.
  • Review PHP-FPM worker usage and error logs per pool.
  • Verify that database users have privileges only on their own databases.
  • Audit ownership and permissions after updates, migrations and restores.
  • Test administrator login, permalinks, uploads and scheduled tasks after PHP or Nginx changes.
  • Run an off-site backup restore for at least one site on a schedule, then rotate credentials used by the restored copy.
  • Document who can access the VPS and remove unused shells, keys and service accounts.

With this layout, a hostname selects one Nginx root, PHP executes under that site’s Unix identity through a private socket, and database credentials stop at that site’s database. That is the practical foundation for hosting multiple WordPress sites on one LEMP server while keeping failures and compromises from automatically becoming everyone else’s problem.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.