Deploying Multiple Projects on One VPS: The Nginx Structure I Use

I run several side projects together on the same VPS — this personal site, a few small client apps I self-host for them, and a handful of smaller domains for experiments. There are roughly seven or eight active domains on the same $6/month server. Renting a separate server per project would be expensive multiplied out that many times, and wasteful for projects with small traffic, so I use a single VPS with Nginx as a reverse proxy and static file server for all of them.
The structure I use is fairly standard, but I got more disciplined about it after one mistake nearly took down two client projects at once.
One config file per domain
Each project has its own config file in /etc/nginx/sites-available/, symlinked into sites-enabled/ when active. This makes it easy to enable or disable a project without disturbing the others — just remove the symlink, nginx -t to test, reload. Early on I tried keeping all domains in one big config file to “see everything at once,” which turned out to be a bad idea: one typo in a single domain’s server block made nginx -t fail for the entire file, meaning every other domain also couldn’t reload until I fixed that one typo. After that I split everything into separate files.
Static site vs. proxy, clearly separated
Static projects (like this site) point root directly at the build output directory. Projects that need a backend (a client app with a Node or Spring Boot API, say) use proxy_pass to the local port the app runs on. I keep port numbering in a separate note so two apps don’t somehow end up fighting over the same port when I add a new project — that happened once, and debugging it took a while before I realized it was just a plain port conflict.
SSL per domain, managed by Certbot
Each domain has its own certificate, auto-renewed via Certbot through a cron job. I deliberately don’t use one wildcard cert for everything — it sounds simpler, but if the wildcard expires or hits a verification issue, every domain goes down together. Certbot per domain means if one domain fails to renew (usually because its DNS is having issues on the client’s end), only that domain is affected, not all of them.
Resource isolation when needed
For heavier applications — usually ones with background jobs or processes prone to eating memory — I run them in a separate Docker container, with Nginx still acting as the single entry point proxying to each container’s port. That way, one app suddenly leaking memory doesn’t drag down other apps running natively on the same host.
The biggest benefit is genuinely cost efficiency — one $6/month VPS comfortably hosts seven or eight small-to-medium projects. But the benefit I didn’t fully appreciate early on is config isolation: if one project needs maintenance or runs into trouble, the others aren’t affected — as long as, as I learned from that big-config-file incident, everything was actually kept separate from the start.