Skip to main content
    VPS

    How to Host Multiple Websites on One VPS (Properly)

    September 13, 2026
    18 min read
    How to Host Multiple Websites on One VPS (Properly)

    The short version of how to host multiple websites on one VPS: give every site five things of its own. A Linux user. A document root inside that user's home directory. A PHP-FPM pool that runs as that user on its own socket. An Nginx server block. A database with a dedicated least-privilege database user. Build that pattern once, script it, and adding site number twelve takes about four minutes.

    The part almost every tutorial skips is the isolation, and it is the part that decides whether a bad afternoon stays small. If all your sites run as www-data out of /var/www, one outdated plugin on your least important site can read every other site's wp-config.php — and therefore every other site's database password. One compromise becomes twelve. Everything below is built around preventing that specific outcome.

    Key Takeaways

    • The unit of isolation is the Linux user, not the folder. One user per site, one PHP-FPM pool per user, one socket per pool, one database user per database.
    • A shared www-data across sites is the most common real-world failure on agency servers. It converts any single plugin vulnerability into a full-server credential leak.
    • Nginx server blocks are the easy part. Ownership, file modes and pool identity are where guides go quiet, and where the mistakes actually live.
    • Per-pool pm.max_children is the throttle that stops one busy site eating all the RAM. Systemd resource control on the FPM service is the backstop under it.
    • Backups have to be per-site restorable. "Roll the whole server back to Tuesday" is not an answer when one client broke one site.
    • Put a client on their own VPS when they have a compliance obligation, unusual traffic, or a realistic chance of being handed over to someone else.

    First Decide: One VPS With Isolation, or a VPS Per Client

    Hosting multiple sites on one server is usually the right call for a portfolio of small clients. Ten small brochure sites on one properly configured 4 GB box is cheaper, easier to patch and easier to monitor than ten 1 GB boxes you forget to update. But there are cases where one box is plainly the wrong call, and it is much cheaper to notice them now than after you have merged everything.

    Give these their own VPS:

    • A client you may hand over. If there is any chance the relationship ends and they take the site to another agency, a dedicated instance turns a painful extraction into a snapshot transfer or a DNS change.
    • Anything with a compliance posture. A client who has to answer a security questionnaire, sign a data processing agreement, or demonstrate that their data is not co-resident with unrelated tenants. You do not want the honest answer to "who else is on this server" to be a list of eleven other businesses.
    • The one site with real traffic. If a single tenant accounts for most of your CPU, everyone else is paying for its spikes and it is being throttled by their idle workers.
    • Anything that needs its own IP reputation. Mostly this means outbound mail. If one tenant gets an IP blacklisted, every site sharing that address inherits the problem.
    • Sites on wildly different stacks. One legacy app pinned to an ancient PHP or MySQL version can hold your whole upgrade path hostage.

    These belong together: low-traffic marketing sites, brochure and portfolio sites, staging and demo environments, internal tools, and the long tail of small retainer clients who each get a few hundred visits a day. That is the natural home for one VPS with proper per-site isolation.

    How to Host Multiple Websites on One VPS Safely: Isolation First

    Here is the failure in full, because it is worth being specific. The default LAMP or LEMP tutorial puts every site under /var/www and runs one PHP-FPM pool as www-data. Every PHP process on the box therefore has the same identity and the same read access.

    An attacker finds a file-read or file-upload bug in a plugin on site7.com — a form builder that has not been updated in two years, typically. They now have code execution as www-data. Their first command is not an exploit; it is cat /var/www/*/wp-config.php. That returns the database host, name, user and password for every WordPress install on the server. From there they connect to MySQL locally and inject admin users or malicious redirects into all of them. No further exploitation is needed. The whole server fell because one plugin on one unimportant site was out of date.

    With per-site users, the same intrusion produces code execution as site7. That user can read /home/site7 and nothing else that matters. The other eleven sites, and their eleven database passwords, are unreachable. You still have an incident, but it is one site's incident, and you can restore that one site.

    Three secondary leaks come from the same shared-identity mistake and are worth closing at the same time: shared PHP session storage (all sites writing session files into one directory, readable by all of them), a shared upload temp directory, and a world-accessible FastCGI socket that lets any local user submit a request to any pool. All three are handled in the pool config below.

    Step 1: One Linux User Per Site, and a Layout That Holds

    Create an unprivileged user per site. Use a naming scheme you can still read in eighteen months — the client short name, not site1.

    sudo adduser --disabled-password --gecos "" acme
    sudo mkdir -p /home/acme/public_html /home/acme/logs /home/acme/tmp/sessions
    sudo chown -R acme:acme /home/acme
    

    Decide the shell deliberately. If the client's developer needs SFTP access, leave a real shell and lock them into their own tree with an SSH match block; if nobody logs in as that user, set the shell to /usr/sbin/nologin. For chrooted SFTP, add a Match User acme block to your SSH config with ChrootDirectory /home/acme and ForceCommand internal-sftp. The classic gotcha: the chroot directory itself must be owned by root and must not be writable by the user, so the writable directories have to be one level down. Nearly every "SFTP connection closed immediately" report traces back to that rule.

    Ownership and permissions that actually hold

    This is the step that gets fudged, and fudging it is how people end up with chmod 777 on an uploads directory at 1 a.m. Set it correctly once:

    sudo chmod 750 /home/acme
    sudo find /home/acme/public_html -type d -exec chmod 750 {} +
    sudo find /home/acme/public_html -type f -exec chmod 640 {} +
    sudo chmod 400 /home/acme/public_html/wp-config.php
    

    Now the problem nobody warns you about: Nginx runs its worker processes as www-data, and it needs to read static files and traverse into /home/acme. With mode 750 and group acme, it cannot. The fix is to add the web server user to each site's group as a supplementary group:

    sudo usermod -aG acme www-data
    sudo systemctl restart nginx
    

    The restart is not optional — supplementary group membership is read when a process starts, so a reload will not pick it up. Be honest with yourself about the trade-off this creates: the Nginx worker can now read every site's files. That is a deliberate choice. The threat you are defending against is a compromised PHP application, which happens constantly; a compromised Nginx worker is rare by comparison. The PHP worker for acme has only acme as its group and cannot touch /home/other-client at all, which is the property you wanted.

    That is also why wp-config.php is set to mode 400. It is readable by its owner only, so the PHP pool running as acme can read it and Nginx cannot. WordPress does not need write access to that file after installation.

    Need more power? Move up to a VPS

    Dedicated resources, full root access, and NVMe storage. From $3.49/mo, ready in minutes.

    Compare VPS plans

    Step 2: A Separate PHP-FPM Pool Per Site

    A pool is a group of PHP worker processes with its own identity, its own socket and its own settings. Running a separate PHP-FPM pool per site is what makes the user separation above mean anything, because it is the pool's user line that decides which Unix account executes the site's code.

    On Debian and Ubuntu the pool files live in /etc/php/8.3/fpm/pool.d/; on the RHEL family they are in /etc/php-fpm.d/. Create acme.conf:

    [acme]
    user = acme
    group = acme
    listen = /run/php/acme.sock
    listen.owner = www-data
    listen.group = www-data
    listen.mode = 0660
    pm = ondemand
    pm.max_children = 8
    pm.process_idle_timeout = 20s
    pm.max_requests = 500
    request_terminate_timeout = 120s
    slowlog = /home/acme/logs/php-slow.log
    request_slowlog_timeout = 10s
    catch_workers_output = yes
    security.limit_extensions = .php
    php_admin_value[error_log] = /home/acme/logs/php-error.log
    php_admin_flag[log_errors] = on
    php_admin_value[memory_limit] = 256M
    php_admin_value[session.save_path] = /home/acme/tmp/sessions
    php_admin_value[upload_tmp_dir] = /home/acme/tmp
    php_admin_value[open_basedir] = /home/acme/
    

    Every directive there is a real PHP-FPM pool directive; if you want the full reference, the PHP manual's FPM configuration page lists them all with defaults. A few deserve explanation:

    • pm = ondemand starts workers only when requests arrive and reaps them after pm.process_idle_timeout. On a box with a dozen mostly-idle sites this is the difference between 12 pools sitting on hundreds of megabytes and 12 pools sitting on almost nothing. Switch your one busy site to pm = dynamic so it keeps warm workers.
    • php_admin_value versus php_value matters. Values set with php_admin_value cannot be overridden by ini_set() in application code. A plugin cannot quietly raise its own memory limit or move its session path out of your tree.
    • open_basedir is a guardrail, not a security boundary. It stops sloppy code from wandering; it is not a substitute for the Unix permissions in step 1. Some packages installed system-wide need /usr/share/php/ appended to the list, so if a library suddenly fails to load, check here first.
    • security.limit_extensions = .php means the pool refuses to execute anything else, which blunts a whole class of upload tricks that end in .phtml or a double extension.

    Delete or disable the default www.conf pool once your per-site pools exist. Leaving it enabled means there is still a pool running as www-data, and sooner or later a server block will point at it by accident.

    Reload with sudo systemctl reload php8.3-fpm, then verify the sockets came up: ls -l /run/php/. You should see one socket per site, each owned by www-data with mode srw-rw----. If a socket is missing, sudo systemctl status php8.3-fpm and the FPM error log will name the pool that failed to parse.

    Why the socket permissions matter more than they look

    A FastCGI socket is not a passive file. Anything that can write to it can submit a request to that pool and have PHP execute a script as the pool's user. If you set listen.mode = 0666 — and plenty of copy-paste configs do, because it makes a permissions error go away — then the other-client user can hand a crafted request to the acme pool and run code as acme. You have just undone the whole design.

    Owning the socket as www-data with mode 0660 means exactly one process can talk to it: the web server. That is the intended path and nothing else.

    Step 3: Nginx Server Blocks for Multiple Sites

    An Nginx server block is what Apache calls a virtual host: it maps an incoming Host header to a document root and a backend. On Debian and Ubuntu, put one file per site in /etc/nginx/sites-available/ and symlink it into sites-enabled. With packages from nginx.org or on the RHEL family, drop the file straight into /etc/nginx/conf.d/.

    Start with the plain HTTP block, which exists only to answer ACME challenges and redirect everything else:

    server {
        listen 80;
        listen [::]:80;
        server_name acme.com www.acme.com;
        root /home/acme/public_html;
        location /.well-known/acme-challenge/ { allow all; }
        location / { return 301 https://$host$request_uri; }
    }
    

    Then the real block:

    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        http2 on;
        server_name acme.com www.acme.com;
        root /home/acme/public_html;
        index index.php index.html;
        access_log /home/acme/logs/access.log;
        error_log /home/acme/logs/error.log;
        ssl_certificate /etc/letsencrypt/live/acme.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/acme.com/privkey.pem;
        client_max_body_size 64m;
        location / { try_files $uri $uri/ /index.php?$args; }
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            fastcgi_pass unix:/run/php/acme.sock;
        }
        location ~ /\.ht { deny all; }
    }
    

    Two portability notes. http2 on; is a separate directive that appeared in nginx 1.25.1; on older builds you write listen 443 ssl http2; instead, and the nginx HTTP/2 module documentation is the authority on which form your version wants. And snippets/fastcgi-php.conf is a Debian and Ubuntu packaging convenience. On other builds, replace that include with include fastcgi_params; plus fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;.

    The line that carries all the isolation is fastcgi_pass unix:/run/php/acme.sock;. Point it at the wrong site's socket and that site's PHP will happily execute this site's files as the wrong user, which produces a confusing permissions failure rather than an obvious error. When you script site creation, this is the field to template most carefully.

    Add a catch-all so unknown hostnames go nowhere

    Without a default server block, the first server block Nginx loads answers every request whose Host header matches nothing — including scanners pointing random domains at your IP, and including any domain someone else parks on your address. Add one file that catches them:

    server {
        listen 80 default_server;
        listen 443 ssl default_server;
        server_name _;
        ssl_reject_handshake on;
        return 444;
    }
    

    ssl_reject_handshake was added in nginx 1.19.4 and lets a default HTTPS block refuse the handshake without needing a dummy certificate. return 444 closes the connection with no response at all.

    Validate before you reload, every time: sudo nginx -t, then sudo systemctl reload nginx. A reload with a broken config on a box holding twelve client sites takes all twelve down.

    The Apache virtual host equivalent

    If you are on Apache, the same isolation is achievable but only with the event or worker MPM plus mod_proxy_fcgi. The old prefork-plus-mod_php setup cannot run different sites as different users, because mod_php executes inside the Apache process. Enable the modules with sudo a2enmod proxy_fcgi setenvif, then per site:

    <VirtualHost *:443>
        ServerName acme.com
        ServerAlias www.acme.com
        DocumentRoot /home/acme/public_html
        SSLEngine on
        SSLCertificateFile /etc/letsencrypt/live/acme.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/acme.com/privkey.pem
        <FilesMatch "\.php$">
            SetHandler "proxy:unix:/run/php/acme.sock|fcgi://localhost/"
        </FilesMatch>
        <Directory /home/acme/public_html>
            AllowOverride All
            Require all granted
        </Directory>
        ErrorLog /home/acme/logs/apache-error.log
        CustomLog /home/acme/logs/apache-access.log combined
    </VirtualHost>
    

    Enable with sudo a2ensite acme and check with sudo apachectl configtest. The text after the pipe in the SetHandler value is a required part of the syntax, not a hostname you need to resolve.

    Step 4: TLS Certificates Across Many Domains

    Issue one certificate per site, not one giant certificate listing every domain on the server. Let's Encrypt permits up to 100 identifiers on a single certificate, but a shared certificate is operationally fragile: if one client's DNS breaks, renewal fails for the whole certificate and every site on it starts serving an expired chain. Separate certificates also mean handing a client over is a file copy, not a re-issue.

    If you want certbot to write the Nginx configuration for you: sudo certbot --nginx -d acme.com -d www.acme.com.

    If you would rather keep your own hand-written server blocks untouched — which is what most people running many sites want — use the webroot authenticator and name the certificate explicitly:

    sudo certbot certonly --webroot -w /home/acme/public_html --cert-name acme.com -d acme.com -d www.acme.com
    

    The webroot method writes a challenge file under /.well-known/acme-challenge/ and needs it served over plain HTTP, which is why that location sits above the redirect in the port 80 block earlier. If validation fails with a 404 or a 301, that ordering is almost always the reason.

    Renewal is usually already automated. Modern certbot installs ship a systemd timer or a cron entry; confirm yours with systemctl list-timers | grep certbot and test the whole path with sudo certbot renew --dry-run. One thing certbot will not do for you when you used certonly: reload the web server after a renewal. Add an executable script at /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh containing systemctl reload nginx, and it runs after every successful renewal. Without it, certificates renew on disk and Nginx keeps serving the old ones until something restarts it — a failure that shows up 60 days later on a Sunday.

    Two commands worth knowing when you run many sites: sudo certbot certificates lists every certificate with its domains and expiry, which is your audit view, and sudo certbot delete --cert-name acme.com cleanly removes a departed client's certificate and its renewal config. Leaving orphaned renewal configs behind is how you end up with renewal failure emails for domains you no longer host. The certbot user guide documents all of these.

    Step 5: One Database and One Database User Per Site

    The same principle, one layer down. A single shared database user, or worse an application connecting as root, means a credential leaked from any site is a credential for every site's data.

    CREATE DATABASE acme_wp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    CREATE USER 'acme_wp'@'localhost' IDENTIFIED BY 'a-long-random-password';
    GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
          CREATE TEMPORARY TABLES, LOCK TABLES, REFERENCES
          ON acme_wp.* TO 'acme_wp'@'localhost';
    

    Note what is not there: no ON *.*, no GRANT ALL, no % wildcard host, no GRANT OPTION. The user can do everything WordPress or Laravel needs inside its own schema and has no visibility into any other. FLUSH PRIVILEGES is not required after GRANT; that is a habit left over from editing grant tables directly.

    Also confirm the database server is not listening on the public interface. ss -tlnp | grep 3306 should show 127.0.0.1, not 0.0.0.0. If a client's developer needs remote access, give them an SSH tunnel rather than opening the port — more on that in our VPS security hardening checklist.

    Step 6: Resource Fairness Between Sites

    Isolation stops sites reading each other. It does nothing to stop one site consuming the whole machine. That is a separate job, and pm.max_children is the main lever.

    Each PHP worker holds real memory, so the total number of workers you allow across all pools multiplied by the memory a worker actually uses has to fit in RAM after the OS, the database and Nginx have taken their share. Measure rather than guess: ps --no-headers -o rss,cmd -C php-fpm8.3 shows the resident size of each live worker in kilobytes. The full arithmetic, including how to budget the database, is in our guide to how many websites you can host on a VPS — use that method rather than copying anyone's numbers, because a lean brochure site and a WooCommerce store differ by a factor of four or more.

    Practical settings that work well on a mixed box:

    • Small, sleepy sites: pm = ondemand with pm.max_children around 4 to 6. They consume nothing between visits.
    • The one site with real traffic: pm = dynamic with a max children value sized from your measurement, plus pm.start_servers and the spare-server settings so it is not cold-starting workers under load.
    • Anything with heavy background jobs: process.priority set to a positive number (5 is reasonable) so its workers yield CPU to interactive requests from other sites.
    • Everywhere: request_terminate_timeout so a single hung request cannot occupy a worker indefinitely, and pm.max_requests so a slow leak gets recycled away.

    A hard backstop with systemd

    Worker counts are a soft limit. If you want a guarantee that PHP as a whole cannot push the machine into swap and take SSH and MySQL down with it, add a systemd drop-in for the FPM service at /etc/systemd/system/php8.3-fpm.service.d/limits.conf:

    [Service]
    MemoryMax=2G
    CPUQuota=300%
    

    Apply with sudo systemctl daemon-reload then sudo systemctl restart php8.3-fpm. CPUQuota is expressed relative to one core, so 300% permits three cores' worth of CPU time. Both directives are standard systemd resource control settings and go in the [Service] section.

    Be clear about what this does and does not do. It caps PHP collectively, protecting the database and your SSH session. It does not cap individual sites, because on a standard distro install all pools share one FPM master process and therefore one cgroup. Getting a genuine per-site CPU or memory cap means running a separate FPM master per site as its own systemd unit, each with its own resource limits. That is real work and real ongoing maintenance, and below roughly twenty sites it is rarely worth it — well-chosen pm.max_children values plus this service-wide backstop cover the realistic failure modes.

    Step 7: Backups You Can Restore One Site From

    The requirement is never "restore the server". It is "restore this one client's site to how it was on Tuesday, without touching the other eleven". Design backwards from that sentence.

    Whole-disk snapshots from your provider are excellent disaster recovery and useless for this. Rolling a snapshot back to Tuesday also rolls back eleven other sites that were fine. Snapshots are your answer to "the server is gone"; per-site backups are your answer to everything else. Run both.

    A per-site job needs two artefacts, taken together and stored together:

    • A files archive of that site's home directory, for example tar -czf acme-files-2026-09-13.tar.gz -C /home acme.
    • A database dump of that site's schema only: mysqldump --single-transaction --quick acme_wp | gzip > acme-db-2026-09-13.sql.gz. The --single-transaction flag gives a consistent InnoDB snapshot without locking the site.

    Store them under a per-site path in off-server object storage, so a restore is a matter of pulling one client's folder rather than sifting a monolithic archive. Keep daily copies for a couple of weeks and weekly copies for longer. Encrypt before upload if the archives contain personal data, and keep the credentials for the backup destination write-only if your provider supports it, so an attacker with root on the VPS cannot delete your history.

    Then the part everyone skips: restore one site into a scratch directory on a test box and load its database, on a schedule. A backup you have not restored is a hypothesis. This is doubly true for multi-tenant boxes, where a restore script that assumes a single site quietly produces a broken result on site five.

    Manual, Free Panel or cPanel: Three Ways to Run This

    Everything above is the manual path. It is not the only sane choice, and the right answer depends far more on who else touches the server than on technical preference.

    Approach Cost Isolation quality Client handoff Maintenance burden
    Manual Nginx + per-site PHP-FPM pools Nothing beyond the VPS The best available, because you set it explicitly and can audit every line Hardest. Handover is a manual tar, dump and DNS exercise you script yourself All yours. OS patching, PHP upgrades, certificate hygiene, adding each site
    Free panel (HestiaCP, CyberPanel, CloudPanel) Free software on the same VPS; needs a bit more RAM Good. These provision a system user and matching PHP-FPM pool per account, though you should confirm the pool's user line on your own install Moderate. Panel-native backups restore cleanly onto another install of the same panel Lower per site, plus a new dependency: the panel itself needs updating and occasionally breaks a config you edited by hand
    cPanel / WHM Licence fee, commonly more than the VPS itself Good, with per-account users and per-account PHP handlers Easiest by a distance. Account transfers between cPanel servers are close to a solved problem Lowest per site; highest financial commitment and least freedom to run a non-standard stack

    A rough decision rule: if you are the only person who will ever touch the server and you are comfortable in a terminal, go manual. If non-technical colleagues or clients need to create email accounts and pull their own backups, a free panel earns its RAM. If you are reselling hosting and expect regular migrations in and out, the cPanel licence buys you a transfer format the whole industry supports — the trade-offs are laid out in our cPanel VPS hosting guide. Mixing approaches is the one thing to avoid: hand-editing configs under a panel that regenerates them will eat your changes.

    Which VPS Size for How Many Sites

    Devoster VPS plans are unmanaged KVM with full root, from Nano at 1 vCPU and 1 GB up to Mega at 10 vCPU and 24 GB, each on Intel Xeon Gold hardware with NVMe storage and one included IPv4. Full root is the prerequisite for everything in this article — you cannot create system users or FPM pools on shared hosting.

    Rather than repeating the sizing arithmetic, size it with the method in the capacity guide and use these as starting points. Nano and Micro are for a single small site, a staging box or an internal tool, not a client portfolio. Starter at 2 vCPU and 4 GB comfortably holds a handful of low-traffic brochure sites with the setup above. Basic and Advanced, at 6 to 8 GB, are the usual home for a working agency portfolio of ten to twenty small sites. Pro and above is where you go when one or two tenants are doing real commerce.

    One warning that catches agencies specifically: disk is often your first ceiling rather than RAM. Uploads accumulate, and if you keep any local backup copies you can burn through 100 GB faster than you expect. Check df -h before you assume RAM is the constraint.

    Plainly: if you do not want to run system updates, read logs and respond when something breaks, an unmanaged VPS is the wrong product no matter how good this guide is. Managed hosting or reseller hosting exists precisely for that case, and it is a legitimate answer. Our VPS hosting plans assume you want the root prompt.

    When You Have Outgrown One VPS

    Consolidation has a natural end. These are the honest signals, and none of them are subtle once you look for them:

    • Swap is in continuous use and the journal shows the OOM killer terminating PHP workers. You are past capacity, not near it.
    • Steal time in top sits consistently high, meaning the physical host is oversubscribed and no configuration change on your side fixes it.
    • One tenant's requirements now dictate everyone's PHP version, timeout values or upgrade schedule.
    • A single-site restore takes longer than what you have promised clients, because the archives got too big.
    • A client asks who else is on the server, and you do not want to answer.
    • You have started avoiding reboots. Fear of your own infrastructure is a capacity signal.

    The usual next move is not a bigger box. It is splitting the largest or most sensitive tenant onto their own VPS and leaving the long tail where it is. That keeps the economics of consolidation for the sites that benefit from it, and gives the one client who needs separation the separation they need.

    Learning how to host multiple websites on one VPS properly is mostly learning one habit: every site gets its own user, its own pool, its own socket, its own database user and its own restorable backup. Configure that once, wrap it in a script, and the twelfth site costs you a few minutes instead of a few hours — and the day one of them gets compromised, it stays one site's problem.

    Have questions? Get in touch

    Not sure which plan fits or how crypto billing works for you? We're here to help.

    Contact us

    Frequently Asked Questions

    How many websites can I host on one VPS?

    There is no fixed number, because a static brochure site and a busy WooCommerce store differ by an order of magnitude. The real limits are RAM divided by PHP worker size, disk space, and your own ability to keep everything patched. Size it from measured worker memory and expected concurrency using the method in our capacity guide rather than from a site count.

    Can I host multiple websites on one VPS without cPanel?

    Yes, and it is what most developers do. Nginx server blocks plus one PHP-FPM pool per site gives you better isolation than many panels and costs nothing in licence fees. What you give up is a web interface for clients and easy account migration between servers. If nobody but you administers the box, a panel adds complexity rather than removing it.

    Do I need a separate IP address for each website?

    No. Name-based virtual hosting has been standard for two decades, and SNI lets a single IP serve HTTPS for many domains. The exceptions are rare: some very old TLS clients, and cases where a site needs its own outbound mail reputation. One IPv4 address is enough for dozens of sites on a single VPS.

    Is one PHP-FPM pool for all sites really that bad?

    Yes, on a multi-tenant box. One pool means one Unix user, so every site's PHP can read every other site's configuration files and database credentials. A single vulnerable plugin then compromises everything on the server rather than one site. A separate PHP-FPM pool per site is the difference between an incident and a disaster.

    Can different sites run different PHP versions on the same VPS?

    Yes. Install multiple FPM packages side by side (Debian and Ubuntu users typically add the Sury repository for this), give each site a pool under the version it needs, and point that site's fastcgi_pass at the matching socket. It is the cleanest way to keep one legacy client from blocking upgrades for everyone else.

    What is the fastest way to add a new site once the server is set up?

    Script it. A shell script that takes a site name and domain, then creates the user and directories, writes the pool file from a template, writes the server block, creates the database and user, requests the certificate and reloads both services, turns a 30-minute job into a 30-second one. It also removes the step-skipping that causes permission bugs.

    Does hosting several sites on one VPS hurt SEO?

    Sharing an IP address is not itself a ranking factor. What can hurt is the shared fate underneath: if one site's traffic slows the server, every site's response time suffers, and downtime affects all of them at once. The resource limits and per-site pools described above are the practical defence.

    Ready to Experience Devoster?

    Join thousands of satisfied customers with transparent pricing and lightning-fast hosting.

    We value your privacy

    We use essential cookies to make our site work, and optional analytics cookies to understand how you use Devoster and improve our services. You can accept all cookies, or adjust your preferences.

    Read more in our Cookie Policy and Privacy Policy. You can change your choices at any time.