Running several websites on one VPS can be a sensible middle ground between shared hosting and a fleet of separate servers. You keep control of the machine, consolidate routine operations, and make better use of capacity that would otherwise sit idle. The trade-off is that you become responsible for the boundaries between applications.
This guide describes a conservative layout for small sites and application workloads. The exact commands depend on your Linux distribution, web server, runtime versions, and provider. Treat configuration examples as starting points: test them in a disposable environment and confirm each service’s current documentation before applying them to a live server.
When one VPS makes sense
One VPS is a reasonable fit when the sites have compatible operational needs, traffic is moderate, and a single maintenance boundary is acceptable. It works well for a portfolio of low-to-medium traffic sites, internal tools, campaign sites, or applications that already share a region and backup policy.
Use separate servers when one workload has unpredictable resource demand, a different compliance boundary, a very different maintenance window, or a failure would be unacceptable for the others. Containers can provide a stronger packaging boundary, but they do not remove the need to manage the host, network, storage, and backups.
Start with capacity, not domain count
Count the resources each application actually needs. CPU is only one constraint: memory pressure, disk I/O, inode usage, and database connections can become the limiting factors first.
Leave operating headroom for spikes, package updates, logs, and the database. A useful first pass is to record each site’s normal and peak memory use, its PHP-FPM or application worker count, database size and growth, and the storage required for backups. Do not promise a capacity number without measuring the workload; a small WordPress site and a queue-heavy Laravel application have very different shapes.
Monitor the host before adding another site. Sustained swap activity, full disks, high load during ordinary traffic, or a database that regularly consumes the available memory are signals to resize or split the workload.
Point domains and DNS at the server
Create DNS records for each hostname and point them at the server’s public address. Keep the DNS record names and the virtual-host names aligned. If you use www, decide whether it is the canonical hostname and redirect the other form consistently.
DNS changes can be cached for the record’s TTL. Verify resolution from more than one network before treating a new site as reachable, and do not expose an administration service merely to make DNS testing easier.
Use one Nginx server block per site
Nginx routes a request to a server block using the hostname. A minimal site definition might look like this:
server {
listen 80;
server_name example.com www.example.com;
root /srv/www/example.com/current/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php-fpm-example.sock;
}
}
Keep each site’s configuration in its own file and enable it using the conventions of your distribution. Validate with nginx -t before reloading. The default_server should not accidentally serve a private application’s files for an unknown hostname; use an explicit default response where appropriate.
Isolate files and application users
Give each application its own directory, Unix user, group, and deploy path. A common layout separates immutable releases from writable directories:
/srv/www/example.com/
├── current -> releases/2026-10-29-1200/
├── releases/
└── shared/
├── storage/
└── .env
The web process should be able to read the release and write only to directories that genuinely require it, such as uploads, caches, or framework storage. Never make the entire web root world-writable. Keep environment files outside the public document root and restrict their permissions to the application user and approved administrators.
Give PHP-FPM pools clear boundaries
For PHP applications, separate pools let you assign a Unix user, socket, worker limits, and environment behavior per site. A pool might begin with settings like these:
[example]
user = example
group = example
listen = /run/php/php-fpm-example.sock
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
These values are illustrative, not a sizing recommendation. More workers consume more memory. Measure response time, queueing, and memory use before increasing them. For Node.js, Python, or other runtimes, apply the same principle: one service identity and one supervised process definition per application.
Separate databases and credentials
Create a database and a least-privileged database user for each application. Separate databases reduce accidental cross-application access and make backup and restore decisions clearer. Store credentials per application, rotate them deliberately, and do not reuse a privileged database account in application configuration.
Database separation is not a substitute for host security. Restrict database listeners to the interfaces they need, keep administrative access off the public internet where possible, and test a restore rather than assuming a dump is usable.
Add TLS only after the hostname is correct
Once DNS resolves and Nginx answers for the intended hostname, issue a certificate using a supported ACME client. Redirect HTTP to HTTPS only after the HTTPS virtual host is working and renewal is tested. Keep certificate renewal observable: an expired certificate can take every site on the machine offline from a user’s perspective.
Do not copy a certificate between unrelated sites unless the certificate explicitly covers those names. Keep private keys readable only by the services that need them.
Monitor, back up, and rehearse recovery
At minimum, watch disk space, memory, load, service health, certificate expiry, database errors, and application logs. Alerts should identify the host, application, signal, and next useful action.
Back up application files, databases, configuration, and any object storage separately when appropriate. Keep a copy outside the VPS and define a retention policy. A backup that has never been restored is an intention, not evidence. Schedule a restore rehearsal to a disposable location and record how long recovery takes.
Common mistakes
- Putting every site under one Unix user, which turns one file permission error into a cross-site access problem.
- Letting a default Nginx server block expose the wrong application for an unknown hostname.
- Giving every PHP-FPM pool an unlimited worker budget and exhausting memory together.
- Keeping uploads, releases, logs, and backups in one directory until the disk fills.
- Treating DNS, TLS, application deployment, and database migration as one unobserved change.
- Assuming a successful backup command proves that recovery will work.
Know when to split the server
Move to separate servers when resource contention is regular, the applications need independent maintenance or security boundaries, or the blast radius is larger than the operational convenience. Containers can be the next step when you need repeatable packaging and process isolation, provided your team can operate the additional layer.
The right arrangement is the one you can observe, update, back up, and recover with confidence. Consolidation is useful only while the boundaries remain understandable.