Host multiple websites with NGINX and HTTPS
Install NGINX on Ubuntu 26.04, configure two independent websites, verify routing, and enable HTTPS with Certbot.
This guide configures two static websites on one Ubuntu 26.04 VPS. Each domain gets its own document root, NGINX server block, access log, error log, and TLS certificate.
Replace app.example.com and docs.example.net everywhere with domains you control.
Prerequisites
- An Ubuntu 26.04 VPS with a non-root sudo user
- The VPS security baseline
- An A record for each domain pointing to the VPS IPv4 address
- An AAAA record for each domain only when IPv6 is configured on the VPS
- TCP ports 80 and 443 allowed by any provider firewall
Install dig if it is not already available (sudo apt install dnsutils). Check the DNS A records before requesting certificates:
dig +short A app.example.com
dig +short A docs.example.net
Both results must contain this VPS’s public IPv4 address. If you configured AAAA records, check each domain with dig +short AAAA and confirm that the result contains the VPS’s public IPv6 address.
Install NGINX and Certbot
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx
sudo systemctl enable --now nginx
sudo systemctl status nginx --no-pager
If UFW is active, allow SSH before enabling it, then allow web traffic:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose
Keep the provider firewall and UFW rules in sync.
Create separate website directories
sudo install -d -o "$USER" -g www-data -m 0755 /var/www/app.example.com/public
sudo install -d -o "$USER" -g www-data -m 0755 /var/www/docs.example.net/public
Create /var/www/app.example.com/public/index.html:
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Application site</title></head>
<body><h1>Application site</h1></body>
</html>
Create /var/www/docs.example.net/public/index.html:
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Documentation site</title></head>
<body><h1>Documentation site</h1></body>
</html>
The files only need read access for the www-data group. Do not make the document roots world-writable.
Configure the first domain
Create /etc/nginx/sites-available/app.example.com:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
root /var/www/app.example.com/public;
index index.html;
access_log /var/log/nginx/app.example.com.access.log;
error_log /var/log/nginx/app.example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Configure the second domain
Create /etc/nginx/sites-available/docs.example.net:
server {
listen 80;
listen [::]:80;
server_name docs.example.net;
root /var/www/docs.example.net/public;
index index.html;
access_log /var/log/nginx/docs.example.net.access.log;
error_log /var/log/nginx/docs.example.net.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Enable both sites and remove the default site:
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/app.example.com
sudo ln -s /etc/nginx/sites-available/docs.example.net /etc/nginx/sites-enabled/docs.example.net
sudo unlink /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
nginx -t must succeed before every reload.
Verify host-based routing
Test NGINX locally before involving public DNS or TLS:
curl --fail --header 'Host: app.example.com' http://127.0.0.1/
curl --fail --header 'Host: docs.example.net' http://127.0.0.1/
The first response must contain Application site, and the second must contain Documentation site. If both requests show the same content, inspect the enabled server blocks and their server_name values.
Enable HTTPS
Request one certificate for each domain after DNS works publicly:
sudo certbot --nginx -d app.example.com
sudo certbot --nginx -d docs.example.net
Choose HTTP-to-HTTPS redirects when prompted. Then verify the configuration and both public endpoints:
sudo nginx -t
curl --fail --head https://app.example.com/
curl --fail --head https://docs.example.net/
sudo certbot renew --dry-run
systemctl list-timers 'certbot*'
Certbot updates the matching server blocks and installs a renewal timer. The dry run must succeed.
Deploy and operate the sites
Upload each site only to its own public directory. Test configuration before reloads, and inspect the domain-specific logs when a request fails:
sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 100 /var/log/nginx/app.example.com.error.log
sudo tail -n 100 /var/log/nginx/docs.example.net.error.log
Keep Ubuntu and NGINX updated, back up site content and NGINX configuration, and monitor certificate renewal. Add another site by repeating the directory, server block, routing test, and certificate steps rather than editing an unrelated site.
For server sizing when several sites share a host, see hosting multiple websites with NGINX.
Sources: NGINX request processing, Ubuntu NGINX server blocks, and Certbot with NGINX.