I used to have a separate hosting plan for almost every project. A personal blog on one host, a client site somewhere else, a few side projects scattered around. It was fine until I started counting the invoices every month.
Consolidating everything onto one cloud server with Nginx virtual hosts cut my hosting costs significantly and actually made things easier to manage — one server to update, one set of SSH keys, one place to check logs.
Nginx's virtual host feature routes incoming traffic to the right site based on the domain name. blog.yourdomain.com goes to one web root, clientsite.com goes to another, all running on the same server. Each site gets its own HTTPS certificate, and none of them know about each other.
This guide shows how to set it up from scratch.
I run this on Tencent Cloud Lighthouse. The 2 vCPU / 4 GB RAM plan comfortably hosts 5–10 small websites simultaneously. Consolidating multiple sites onto one Lighthouse instance is particularly cost-effective because the plan includes a fixed monthly bandwidth allowance — so serving traffic from multiple sites doesn't generate per-request bandwidth bills. Lighthouse also has data centers across multiple regions (North America, Europe, Singapore, Tokyo, and more), so if your different sites serve audiences in different regions you can choose the most suitable location. The console-level firewall handles port management for all sites centrally.
Key Takeaways
- Each virtual host in
/etc/nginx/sites-available/handles one domain- Symlink from
sites-available/tosites-enabled/to activate a site- Run Certbot separately for each domain to get individual SSL certificates
- Sites on the same server share the IP — Nginx routes by
server_name- Use different PHP-FPM pools for sites that need different PHP versions
When a browser visits site1.com, it sends an HTTP request with a Host: site1.com header. Nginx reads this header and matches it to the server_name directive in your config files. Each domain gets its own server {} block, pointing to a different web root, backend process, or upstream.
Request: Host: site1.com → Nginx server block: server_name site1.com → /var/www/site1
Request: Host: site2.com → Nginx server block: server_name site2.com → /var/www/site2
Request: Host: api.app.com → Nginx server block: server_name api.app.com → localhost:3000
One server. Multiple domains. Nginx handles the routing.
Virtual hosts work for any type of site or app:
| Site type | How it works |
|---|---|
| Static HTML/CSS | Nginx serves files directly from a directory |
| PHP apps (WordPress, Laravel) | Nginx passes .php requests to PHP-FPM |
| Node.js / Python / Go apps | Nginx proxies to a backend process on a local port |
| React / Vue / static builds | Nginx serves the build directory as static files |
| Different domains per app | Each domain has its own server {} block |
| Requirement | Notes |
|---|---|
| Cloud server | Tencent Cloud Lighthouse Ubuntu 22.04 |
| Nginx installed | sudo apt install nginx |
| DNS configured | Each domain's A record must point to your server's IP |
ssh ubuntu@YOUR_SERVER_IP
sudo apt update
sudo apt install -y nginx
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo systemctl start nginx
sudo systemctl enable nginx
Organize site files under /var/www/, one directory per domain:
# Create web roots
sudo mkdir -p /var/www/site1.com/html
sudo mkdir -p /var/www/site2.com/html
sudo mkdir -p /var/www/blog.example.com/html
# Set ownership
sudo chown -R ubuntu:www-data /var/www/site1.com
sudo chown -R ubuntu:www-data /var/www/site2.com
sudo chown -R ubuntu:www-data /var/www/blog.example.com
# Set permissions
sudo chmod -R 755 /var/www/site1.com
Add placeholder pages to test routing:
echo "<h1>site1.com is working</h1>" | sudo tee /var/www/site1.com/html/index.html
echo "<h1>site2.com is working</h1>" | sudo tee /var/www/site2.com/html/index.html
Create a separate config file for each site in /etc/nginx/sites-available/.
sudo nano /etc/nginx/sites-available/site1.com
server {
listen 80;
server_name site1.com www.site1.com;
root /var/www/site1.com/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/site1.com_access.log;
error_log /var/log/nginx/site1.com_error.log;
}
sudo nano /etc/nginx/sites-available/site2.com
server {
listen 80;
server_name site2.com www.site2.com;
root /var/www/site2.com/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~ /\.ht {
deny all;
}
access_log /var/log/nginx/site2.com_access.log;
error_log /var/log/nginx/site2.com_error.log;
}
sudo nano /etc/nginx/sites-available/api.myapp.com
server {
listen 80;
server_name api.myapp.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
access_log /var/log/nginx/api.myapp.com_access.log;
error_log /var/log/nginx/api.myapp.com_error.log;
}
# Enable each site (create symlinks)
sudo ln -s /etc/nginx/sites-available/site1.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site2.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/api.myapp.com /etc/nginx/sites-enabled/
# Remove the default site
sudo rm -f /etc/nginx/sites-enabled/default
# Test configuration
sudo nginx -t
# Reload
sudo systemctl reload nginx
Get certificates for each domain. Certbot handles each domain independently:
sudo apt install -y certbot python3-certbot-nginx
# Get certificates (run once per domain)
sudo certbot --nginx -d site1.com -d www.site1.com
sudo certbot --nginx -d site2.com -d www.site2.com
sudo certbot --nginx -d api.myapp.com
Each domain gets its own certificate. Auto-renewal handles all of them — one cron job manages the entire set.
Here's an example of a full setup with three different app types on one server:
Portfolio site (static HTML) — myportfolio.com
# /var/www/myportfolio.com/html contains built HTML/CSS/JS files
# server_name myportfolio.com; root /var/www/myportfolio.com/html;
WordPress blog (PHP) — myblog.com
# /var/www/myblog.com/html contains WordPress files
# PHP-FPM processes .php files
Node.js API (backend app) — api.myblog.com
# Node.js runs on port 3001
# PM2 keeps it running: pm2 start server.js --name "blog-api"
# Nginx proxies api.myblog.com to 127.0.0.1:3001
All three run simultaneously on the same server, completely isolated from each other by Nginx's routing.
When multiple sites share a server, resource management matters.
# Overall server resource usage
htop
# Nginx connections per site
sudo tail -f /var/log/nginx/site1.com_access.log
sudo tail -f /var/log/nginx/site2.com_access.log
# Disk usage per site
du -sh /var/www/*
For PHP sites, you can run separate PHP-FPM pools per domain with resource limits:
sudo cp /etc/php/8.2/fpm/pool.d/www.conf /etc/php/8.2/fpm/pool.d/site2.conf
sudo nano /etc/php/8.2/fpm/pool.d/site2.conf
[site2]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm-site2.sock
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
Then reference this socket in the Nginx config for site2:
fastcgi_pass unix:/run/php/php8.2-fpm-site2.sock;
When Nginx doesn't find a matching server_name for a request (e.g., someone visits your server's raw IP, or a bot probes a random hostname), it falls back to the default server — which is usually the first site alphabetically in sites-enabled/.
This means site1.com might serve requests for site3.com if site3.com isn't configured yet.
Define an explicit default server to handle unmatched requests:
sudo nano /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444; # Connection closed without response
}
sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# Add a new site
sudo mkdir -p /var/www/newsite.com/html
sudo nano /etc/nginx/sites-available/newsite.com
sudo ln -s /etc/nginx/sites-available/newsite.com /etc/nginx/sites-enabled/
sudo certbot --nginx -d newsite.com
sudo nginx -t && sudo systemctl reload nginx
# Disable a site (keep config, stop serving)
sudo rm /etc/nginx/sites-enabled/site1.com
sudo systemctl reload nginx
# Re-enable a site
sudo ln -s /etc/nginx/sites-available/site1.com /etc/nginx/sites-enabled/
sudo systemctl reload nginx
# List active sites
ls /etc/nginx/sites-enabled/
# Test all configs
sudo nginx -t
# View all access logs
sudo tail -f /var/log/nginx/*.com_access.log
| Issue | Likely Cause | Fix |
|---|---|---|
| Connection refused | Service not running or wrong port | Check systemctl status SERVICE and verify firewall rules |
| Permission denied | Wrong file ownership or permissions | Check file ownership with ls -la and use chown/chmod to fix |
| 502 Bad Gateway | Backend service not running | Restart the backend service; check logs with journalctl -u SERVICE |
| SSL certificate error | Certificate expired or domain mismatch | Run sudo certbot renew and verify domain DNS points to server IP |
| Service not starting | Config error or missing dependency | Check logs with journalctl -u SERVICE -n 50 for specific error |
| Out of disk space | Logs or data accumulation | Run df -h to identify usage; clean logs or attach CBS storage |
| High memory usage | Too many processes or memory leak | Check with htop; consider upgrading instance plan if consistently high |
| Firewall blocking traffic | Port not open in UFW or Lighthouse console | Open port in Lighthouse console firewall AND sudo ufw allow PORT |
How many websites can I host on one cloud server?
There's no fixed limit — it depends on your server's RAM and CPU. A 2 GB RAM server comfortably runs 5–10 low-traffic sites. Monitor resource usage with htop and upgrade the server spec if needed.
Do all sites on the same server share the same IP address?
Yes. Nginx uses the server_name directive to route traffic to the right site based on the domain name. All sites share the single server IP.
Can I use different PHP versions for different sites on the same server?
Yes. Install multiple PHP-FPM versions (e.g., php7.4-fpm and php8.2-fpm) and configure each Nginx virtual host to use the appropriate PHP-FPM socket.
How do I get SSL certificates for multiple domains?
Run Certbot once per domain: sudo certbot --nginx -d site1.com, then sudo certbot --nginx -d site2.com. Each domain gets its own certificate with its own auto-renewal schedule.
Host all your projects on one server:
👉 Tencent Cloud Lighthouse — Flexible VPS for multiple websites
👉 View current pricing and promotions
👉 Explore all active deals and offers