Key Takeaways
sites-availablevssites-enabled: Config files live insites-available/. Enabling a site creates a symlink insites-enabled/. Nginx only loads enabled sites. Disabling isrmora2dissite-equivalent:unlink /etc/nginx/sites-enabled/mysite.server_nameroutes requests: Nginx matches the HTTPHostheader against everyserver_namedirective to pick the correct block. Missingwww.or other aliases means those requests fall through to the default server.nginx -tbefore every reload: Test syntax before applying. A single typo in an unrelated file can bring down all sites on the server.- One file per domain: Keep each domain in its own file — easier to enable, disable, version-control, and debug than a monolithic
nginx.conf.
Introduction
Direct Answer: How do I configure Nginx server blocks to host multiple domains on Ubuntu 24.04?
Create a config file for each domain in /etc/nginx/sites-available/. A minimal server block contains: server { listen 80; server_name domain.com www.domain.com; root /var/www/domain.com; index index.html; location / { try_files $uri $uri/ =404; } }. Save as /etc/nginx/sites-available/domain.com, enable with sudo ln -s /etc/nginx/sites-available/domain.com /etc/nginx/sites-enabled/, disable the default site with sudo unlink /etc/nginx/sites-enabled/default, test with sudo nginx -t, and reload with sudo systemctl reload nginx. Create the document root with sudo mkdir -p /var/www/domain.com and set ownership with sudo chown -R www-data:www-data /var/www/domain.com. Repeat for each additional domain — Nginx matches each incoming request’s Host header to the correct server block. Tested on Ubuntu 24.04 with Nginx 1.27.
Part 1: Create Your First Server Block
# Verify Nginx is installed and running
nginx -v && sudo systemctl is-active nginx
Expected output:
nginx version: nginx/1.27.3
active
# Create the document root for your domain
DOMAIN="example.com"
sudo mkdir -p /var/www/${DOMAIN}/html
sudo chown -R $USER:$USER /var/www/${DOMAIN}
sudo chmod -R 755 /var/www/${DOMAIN}
# Create a test index page
cat > /var/www/${DOMAIN}/html/index.html << EOF
<!DOCTYPE html>
<html>
<head><title>Welcome to ${DOMAIN}</title></head>
<body><h1>${DOMAIN} is live — sovereign and self-hosted.</h1></body>
</html>
EOF
# Create the server block config
sudo tee /etc/nginx/sites-available/${DOMAIN} << EOF
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN} www.${DOMAIN};
root /var/www/${DOMAIN}/html;
index index.html index.htm;
access_log /var/log/nginx/${DOMAIN}-access.log;
error_log /var/log/nginx/${DOMAIN}-error.log;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
# Enable the site
sudo ln -sf /etc/nginx/sites-available/${DOMAIN} /etc/nginx/sites-enabled/${DOMAIN}
# Disable the default catch-all (optional — keeps things clean)
sudo unlink /etc/nginx/sites-enabled/default 2>/dev/null || true
# Test and reload
sudo nginx -t
Expected output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx
# Test locally (add to /etc/hosts for local testing without DNS)
echo "127.0.0.1 example.com www.example.com" | sudo tee -a /etc/hosts
curl -s http://example.com | grep -o "<h1>.*</h1>"
Expected output:
<h1>example.com is live — sovereign and self-hosted.</h1>
Part 2: Add a Second Domain
DOMAIN2="app.example.com"
sudo mkdir -p /var/www/${DOMAIN2}/html
sudo chown -R $USER:$USER /var/www/${DOMAIN2}
cat > /var/www/${DOMAIN2}/html/index.html << EOF
<!DOCTYPE html>
<html><body><h1>App subdomain — ${DOMAIN2}</h1></body></html>
EOF
sudo tee /etc/nginx/sites-available/${DOMAIN2} << EOF
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN2};
root /var/www/${DOMAIN2}/html;
index index.html;
access_log /var/log/nginx/${DOMAIN2}-access.log;
error_log /var/log/nginx/${DOMAIN2}-error.log;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/${DOMAIN2} /etc/nginx/sites-enabled/${DOMAIN2}
sudo nginx -t && sudo systemctl reload nginx
# Verify both domains are served independently
echo "127.0.0.1 app.example.com" | sudo tee -a /etc/hosts
curl -s http://example.com | grep h1
curl -s http://app.example.com | grep h1
Expected output:
<h1>example.com is live — sovereign and self-hosted.</h1>
<h1>App subdomain — app.example.com</h1>
Two domains, one server, independently served.
Part 3: PHP Site Server Block (WordPress / Laravel)
# Install PHP-FPM for dynamic PHP sites
sudo apt-get install -y php8.3-fpm php8.3-mysql php8.3-xml php8.3-curl
sudo tee /etc/nginx/sites-available/wordpress.example.com << 'EOF'
server {
listen 80;
server_name wordpress.example.com;
root /var/www/wordpress.example.com;
index index.php index.html;
access_log /var/log/nginx/wordpress-access.log;
error_log /var/log/nginx/wordpress-error.log;
# WordPress pretty permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# Pass PHP files to PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
# Block access to dotfiles (.env, .git, etc.)
location ~ /\. {
deny all;
return 404;
}
# Block PHP execution in uploads directory
location ~* /(?:uploads|files)/.*\.php$ {
deny all;
}
# Static file caching
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
client_max_body_size 64M; # WordPress media uploads
}
EOF
sudo ln -sf /etc/nginx/sites-available/wordpress.example.com \
/etc/nginx/sites-enabled/wordpress.example.com
sudo nginx -t && sudo systemctl reload nginx
Part 4: Proxy Server Block (Node.js / Python Backend)
sudo tee /etc/nginx/sites-available/api.example.com << 'EOF'
server {
listen 80;
server_name api.example.com;
access_log /var/log/nginx/api-access.log;
error_log /var/log/nginx/api-error.log;
location / {
proxy_pass http://127.0.0.1:3000;
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;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 60s;
}
location /static/ {
alias /var/www/api.example.com/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/api.example.com \
/etc/nginx/sites-enabled/api.example.com
sudo nginx -t && sudo systemctl reload nginx
Part 5: List and Manage Server Blocks
# List all available sites
echo "=== AVAILABLE SITES ==="
ls -la /etc/nginx/sites-available/
# List all enabled sites (symlinks)
echo ""
echo "=== ENABLED SITES ==="
ls -la /etc/nginx/sites-enabled/
# Check which server_name each block uses
echo ""
echo "=== SERVER NAMES ==="
grep -r "server_name" /etc/nginx/sites-enabled/ | grep -v "^Binary"
# Show active virtual host configuration
sudo nginx -T 2>/dev/null | grep -E "server_name|listen|root" | head -30
Expected output:
=== ENABLED SITES ===
lrwxrwxrwx example.com -> /etc/nginx/sites-available/example.com
lrwxrwxrwx app.example.com -> /etc/nginx/sites-available/app.example.com
=== SERVER NAMES ===
/etc/nginx/sites-enabled/example.com: server_name example.com www.example.com;
/etc/nginx/sites-enabled/app.example.com: server_name app.example.com;
# Disable a site (without deleting the config)
sudo unlink /etc/nginx/sites-enabled/app.example.com
sudo nginx -t && sudo systemctl reload nginx
echo "app.example.com disabled"
# Re-enable it
sudo ln -sf /etc/nginx/sites-available/app.example.com \
/etc/nginx/sites-enabled/app.example.com
sudo nginx -t && sudo systemctl reload nginx
echo "app.example.com re-enabled"
Part 6: Add HTTPS with Let’s Encrypt
sudo apt-get install -y certbot python3-certbot-nginx
# Obtain certificates for both domains (must have DNS pointing to this server)
sudo certbot --nginx \
-d example.com -d www.example.com \
--non-interactive --agree-tos \
--email [email protected] \
--redirect # Auto-redirect HTTP → HTTPS
Expected output:
Successfully received certificate.
Deploying certificate to VirtualHost /etc/nginx/sites-enabled/example.com
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/example.com
Congratulations! You have successfully enabled HTTPS on https://example.com
# Verify SSL renewal works (dry run)
sudo certbot renew --dry-run | tail -5
Expected output:
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/example.com/fullchain.pem (success)
Troubleshooting
nginx: [emerg] a duplicate default server for 0.0.0.0:80
Cause: Two server blocks both have listen 80 default_server — only one server can be the default.
Fix: Remove default_server from all but one server block, or remove the default site: sudo unlink /etc/nginx/sites-enabled/default.
403 Forbidden on static files
Cause: Nginx process (www-data) can’t read the files — ownership or permission mismatch.
Fix:
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com
# Parent directories must also be executable by www-data:
sudo chmod o+x /var/www
nginx: [warn] conflicting server name "example.com" on 0.0.0.0:80
Cause: Same server_name appears in two different config files.
Fix: grep -r "server_name example.com" /etc/nginx/sites-enabled/ to find duplicates. Each domain should appear in exactly one server block.
Conclusion
Multiple domains are now running on a single Nginx server — each with its own document root, log files, and configuration, cleanly separated into individual files in sites-available/. The nginx -t → reload pattern ensures zero-downtime config changes.
The natural next step is securing every server block with HTTPS — see How to Install Nginx on Ubuntu 24.04 LTS for the base installation guide, and Nginx Reverse Proxy Tutorial 2026 to proxy application backends behind these server blocks.
People Also Ask
What is the difference between Nginx server blocks and Apache virtual hosts?
They are functionally identical — both are the mechanism for hosting multiple domains on a single web server. The terminology differs: Nginx calls them “server blocks” (server { } directives); Apache calls them “virtual hosts” (<VirtualHost> directives). The configuration syntax differs significantly, but the concept — matching an incoming request’s Host header to a directory and configuration — is the same. Ubuntu’s Nginx uses the sites-available/sites-enabled pattern borrowed from Apache’s a2ensite workflow.
How many domains can one Nginx server handle?
Nginx has no hard limit on server blocks — production servers routinely host hundreds of domains on a single instance. The practical limits are memory (each worker connection uses ~256KB) and disk I/O for serving files from many different document roots. For a typical Hetzner CX22 (4GB RAM) serving mostly static sites or small dynamic apps, 50–100 active domains is reasonable. Large shared hosting environments run thousands of server blocks on well-tuned hardware.
Can I use wildcards in server_name for subdomains?
Yes: server_name *.example.com; matches any subdomain of example.com. server_name .example.com; matches both example.com and *.example.com in a single directive. Wildcards can only appear at the start or end of a name: server_name *.example.* is not valid. For a wildcard SSL certificate with Let’s Encrypt, use Certbot DNS challenge: sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com".
Part 12: Multi-site and Multi-tenant Server Blocks
NGINX server blocks are the basis for hosting multiple sites securely on one host.
12.1 Host-based routing
Use the server_name directive to route traffic to the correct site.
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Use separate server blocks for each domain and keep TLS config isolated.
12.2 Wildcard and catchall blocks
Reserve a catchall server block for unexpected hosts. This prevents stray requests from landing on the wrong site.
server {
listen 80 default_server;
server_name _;
return 444;
}
12.3 Shared upstreams and proxying
Use upstream groups to share backend pools across server blocks.
upstream app_backend {
server 10.0.0.10:8080;
server 10.0.0.11:8080;
}
server {
listen 443 ssl;
server_name app.example.com;
location / {
proxy_pass http://app_backend;
}
}
This enables consistent load balancing and simplifies backend management.
Part 13: TLS and HTTP/2 in Server Blocks
Secure server blocks with strong TLS and modern protocols.
13.1 Dedicated TLS server blocks
Use separate listen directives for HTTP and HTTPS and redirect all HTTP traffic to HTTPS.
server {
listen 80;
server_name secure.example.com;
return 301 https://$host$request_uri;
}
13.2 TLS session reuse and OCSP
Add TLS session cache and enable OCSP stapling for better performance.
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
13.3 HSTS and preloading
Enable HSTS only after you are sure the domain is fully HTTPS-ready.
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
13.4 HTTP/2 and QUIC
Enable HTTP/2 for HTTPS server blocks. For QUIC, use a compatible NGINX build or a gateway that supports it.
listen 443 ssl http2;
Part 14: Caching and Performance
Server blocks are a great place to implement caching and response optimization.
14.1 Static asset caching
Serve static assets directly from NGINX and apply long cache headers.
location /assets/ {
root /var/www/example.com;
expires 30d;
add_header Cache-Control "public, immutable";
}
14.2 Proxy caching for dynamic content
Use proxy_cache for responses that can be safely reused.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m max_size=1g inactive=60m;
Cache only responses with specific status codes and headers to avoid stale content.
14.3 Compression and content types
Enable gzip and Brotli compression for text-based responses.
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
Be careful with compressing already-compressed formats like images and video.
Part 15: Security in Server Blocks
Server blocks can enforce security boundaries at the edge.
15.1 Strict host validation
Use server_name and a default block to avoid host header confusion.
15.2 Header hardening
Add security response headers in each server block as appropriate.
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
15.3 Request rate limiting
Rate limit on a per-server-block basis for API or login endpoints.
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=20r/s;
Use limit_req within relevant locations.
Part 16: Maintenance and Deployment Patterns
Managing server blocks across multiple sites requires good deployment practices.
16.1 Configuration includes
Keep common server block fragments in include files.
include /etc/nginx/snippets/ssl.conf;
include /etc/nginx/snippets/robotstxt.conf;
This avoids duplication and simplifies updates.
16.2 Staging and smoke tests
Validate each server block in a staging environment, then use nginx -t and smoke-test URLs after deployment.
16.3 Automated certificate renewals
Automate TLS certificate renewals and reload NGINX gracefully when certificates change.
Part 17: Observability and Errors
Server blocks should expose enough data to diagnose issues quickly.
17.1 Custom error logging
Use error logs per site when necessary.
error_log /var/log/nginx/example.com.error.log warn;
17.2 Health checks
Expose internal health endpoints only on private interfaces or via access control.
17.3 Metrics and analytics
Collect request metrics, status codes, and latencies for each server block. This helps you spot misconfigurations and traffic shifts.
Part 18: Legacy and Modern Coexistence
Server blocks can support both legacy and modern clients.
18.1 Protocol negotiation
Support TLS 1.3 while allowing TLS 1.2 for legacy clients if needed. Use a strong cipher list and consider a separate legacy server block if necessary.
18.2 Content negotiation
Serve adaptive content based on client capabilities. Use headers like Accept-Encoding and User-Agent carefully.
18.3 Deprecation planning
When you phase out legacy TLS or protocols, plan the migration and communicate timelines to stakeholders.
Part 19: Final Server Block Checklist
- each domain has a dedicated, minimal server block
- TLS is terminated with strong ciphers and session settings
- static and dynamic caching policies are applied correctly
- security headers are present and consistent
- rate limiting is configured where needed
- configs are reusable, reviewed, and tested
- health checks and metrics are in place
- maintenance and reload procedures are documented
A well-engineered NGINX server block structure scales gracefully and keeps each site secure and performant. The goal is clarity, repeatability, and minimal surface area for errors.
Part 20: Dynamic Services and Service Discovery
NGINX often fronts dynamic services whose addresses can change in real time.
20.1 DNS-based upstream discovery
Use DNS for service discovery when backend endpoints are ephemeral.
resolver 127.0.0.53 valid=30s;
upstream backend {
zone backend 64k;
server backend.internal:8080;
}
This lets NGINX resolve backend addresses dynamically without reloads.
20.2 Health checks and failover
Active health checks keep traffic away from unhealthy backends.
upstream api_backend {
server 10.0.0.10:8080;
server 10.0.0.11:8080;
health_check;
}
20.3 Canary routing and weighted traffic
Use weighted upstreams to route a small percentage of traffic to a new version.
upstream canary {
server 10.0.0.10:8080 weight=9;
server 10.0.0.11:8080 weight=1;
}
This supports progressive rollout strategies.
Part 21: Application Layer Gateways
NGINX can serve as an API gateway with middleware-like behavior.
21.1 Request rewriting and redirects
Rewrite incoming paths for compatibility or deprecation.
location /old-api/ {
rewrite ^/old-api/(.*)$ /new-api/$1 permanent;
}
21.2 Authentication preprocessing
Use NGINX to enforce authentication tokens or headers before proxying to backends. This reduces load on application services.
21.3 Rate limiting and quota enforcement
Apply request quotas at the ingress layer. This prevents abusive clients from consuming all backend capacity.
Part 22: Observability and Diagnostics
Good server block management requires observability.
22.1 Per-site access logging
Store each site’s access logs separately when hosting multiple domains. This simplifies incident investigation.
22.2 Metrics export
Export metrics for request latency, status codes, and upstream errors. This helps identify misrouted traffic or backend degradation.
22.3 Error response consistency
Keep error responses consistent across server blocks. User-facing error behavior should be predictable, even during outages.
Part 23: Container and Kubernetes Patterns
Many NGINX setups now run in containerized or orchestrated environments.
23.1 Config maps and sidecars
In Kubernetes, manage server blocks through ConfigMaps and reload NGINX on change. Keep the NGINX container image minimal.
23.2 Ingress controllers
If using an ingress controller, understand how server blocks are generated. Review the generated config and keep custom snippets maintainable.
23.3 Service mesh interaction
When a service mesh is present, decide whether NGINX should be inside or outside the mesh. Avoid redundant proxying and keep TLS termination boundaries clear.
Part 24: Final NGINX Server Block Checklist
- dynamic upstreams are resolved safely and health-checked
- canary and rollout traffic patterns are supported
- application gateway responsibilities are clearly defined
- observability is enabled per site and per upstream
- Kubernetes or containerized deployments use reusable config fragments
- error handling is consistent and secure
- config reloads are validated and automated where possible
- multi-tenant routing does not expose unintended hostnames
Well-designed NGINX server blocks form the foundation of a scalable, secure, multi-site deployment. Keep them small, composition-friendly, and aligned with the rest of your infrastructure.
Part 25: Large-Scale Deployment Patterns
NGINX server blocks behave differently at scale. Design them for manageability and isolation.
25.1 Shared configuration fragments
Use reusable snippets for security headers, TLS, and proxy settings. Keep site-specific blocks lean by including shared files.
25.2 Version-controlled configuration
Store NGINX server block configurations in Git. Promote changes through staging and production branches with peer review.
25.3 Automated validation
Run syntax checks and configuration validation in CI/CD before deploying new server blocks. Automate smoke tests against staging sites.
Part 26: Edge Caching and Content Delivery
Serve assets and dynamic fragments from the edge when possible.
26.1 Content caching strategies
Cache static assets aggressively. Use conditional caching for dynamic content that can be safely reused.
26.2 Cache purging and invalidation
Provide explicit cache purge endpoints or administration workflows. If content updates frequently, use short cache lifetimes and active invalidation.
26.3 Geo and device-based routing
For globally distributed traffic, route users to the closest backend or edge cache. Use NGINX geo modules or external load balancers to make routing decisions.
Part 27: Security Response and Hardening Iterations
NGINX security should evolve alongside threat models.
27.1 Responsive header tuning
Update security headers as browser and client support evolves. Test changes to avoid breaking legitimate user agents.
27.2 Rate limiting refinement
Refine rate limiting rules based on real traffic patterns. Too strict rules can block valid clients; too loose rules fail to protect the site.
27.3 Endgame controls
For sites with extreme sensitivity, use a fallback maintenance mode or a challenge-response mechanism during suspicious traffic spikes.
Part 28: Modern Protocols and WebSockets
NGINX server blocks should support modern application protocols cleanly.
28.1 HTTP/3 and QUIC
Enable HTTP/3 where supported, and use a compatible NGINX build or proxy. HTTP/3 improves connection resilience for lossy networks.
28.2 WebSocket proxying
Proxy WebSocket connections with the appropriate upgrade headers.
location /ws/ {
proxy_pass http://backend_ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
28.3 gRPC support
For gRPC services, use grpc_pass and a properly configured server block.
location /grpc.service {
grpc_pass grpc://127.0.0.1:50051;
}
Part 29: Service Mesh and API Gateway Patterns
NGINX can coexist with or replace service mesh components depending on your architecture.
29.1 Sidecar vs gateway
Decide whether NGINX is the edge gateway or an internal sidecar. Keep the separation of concerns clear: the gateway handles ingress, while the mesh handles east-west traffic.
29.2 Route rewriting and API shaping
Use server blocks to expose clean API endpoints while rewriting or normalising requests for internal services.
29.3 Authentication at the edge
Terminate authentication tokens and mTLS at the gateway when appropriate. This allows backend services to trust the incoming request without duplicating auth logic.
Part 30: Continuous Operations and Checklists
Operational maturity comes from repeatable processes.
30.1 Change validation
Validate server block changes with both configuration tests and external smoke checks. Use canary deployments when adding new domains or TLS settings.
30.2 Incident response
Document how to rollback a server block change and how to isolate a misbehaving site. Keep rebuild scripts or automated deployments available.
30.3 Security review cycle
Periodically review server block security controls, especially TLS, headers, and access restrictions. Threat models evolve, so the server block portfolio should be reevaluated regularly.
Further Reading
- How to Install Nginx on Ubuntu 24.04 LTS — prerequisite: base Nginx installation
- Nginx Reverse Proxy Tutorial 2026 — proxy Node.js/Python backends behind these server blocks
- UFW Firewall Tutorial 2026 — open ports 80 and 443 for these sites
- Ubuntu 24.04 LTS Server Setup Checklist — server hardening before going live
Tested on: Ubuntu 24.04 LTS (Hetzner CX22). Nginx 1.27.3. Last verified: April 28, 2026.