
How to Set Up Nginx Server Blocks (Virtual Hosts) on Ubuntu 24
Nginx server blocks (also known as virtual hosts in Apache land) are essentially a way to run multiple websites or applications on a single server by telling Nginx which configuration to use based on the incoming domain name or IP address. This is crucial for anyone managing multiple sites or applications on one server – whether you’re hosting client websites, running different environments (staging, production), or just want to organize your services properly. By the end of this guide, you’ll know how to create, configure, and troubleshoot Nginx server blocks on Ubuntu 24, plus some pro tips that’ll save you headaches down the road.
How Nginx Server Blocks Work
Unlike Apache’s virtual hosts that can get pretty resource-heavy, Nginx server blocks are lightweight and efficient. When a request hits your server, Nginx looks at the server_name
directive in your server block configurations and matches it against the Host header in the HTTP request. If there’s a match, it uses that server block’s configuration to serve the content.
The magic happens in the /etc/nginx/sites-available/
directory where you store your server block configurations, and /etc/nginx/sites-enabled/
where you symlink the active ones. This separation lets you keep configurations ready but disabled, which is super handy for maintenance or testing.
Here’s what happens under the hood:
- Client sends HTTP request with Host header (e.g., example.com)
- Nginx checks enabled server blocks for matching
server_name
- If match found, uses that block’s configuration
- If no match, falls back to the default server block
- Serves content from specified document root with applied rules
Prerequisites and Initial Setup
Before diving in, make sure you’ve got Nginx installed on Ubuntu 24. If you’re running this on a VPS or dedicated server, the process is identical.
sudo apt update
sudo apt install nginx
# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
# Check if it's running
sudo systemctl status nginx
You should also verify your firewall allows HTTP and HTTPS traffic:
sudo ufw allow 'Nginx Full'
sudo ufw status
Step-by-Step Server Block Configuration
Let’s create a server block for a site called myapp.local
. In production, this would be your actual domain name.
Step 1: Create Directory Structure
# Create web root directory
sudo mkdir -p /var/www/myapp.local/html
# Set ownership to your user (replace 'username' with your actual username)
sudo chown -R $USER:$USER /var/www/myapp.local/html
# Set proper permissions
sudo chmod -R 755 /var/www/myapp.local
Step 2: Create Sample Content
nano /var/www/myapp.local/html/index.html
Add some basic HTML:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to myapp.local</title>
</head>
<body>
<h1>Success! myapp.local server block is working!</h1>
<p>If you can see this page, your Nginx server block is properly configured.</p>
</body>
</html>
Step 3: Create Server Block Configuration
sudo nano /etc/nginx/sites-available/myapp.local
Here’s a solid basic configuration:
server {
listen 80;
listen [::]:80;
root /var/www/myapp.local/html;
index index.html index.htm index.nginx-debian.html;
server_name myapp.local www.myapp.local;
location / {
try_files $uri $uri/ =404;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
# Disable server tokens for security
server_tokens off;
# Log files
access_log /var/log/nginx/myapp.local.access.log;
error_log /var/log/nginx/myapp.local.error.log;
}
Step 4: Enable the Server Block
# Create symbolic link to enable the site
sudo ln -s /etc/nginx/sites-available/myapp.local /etc/nginx/sites-enabled/
# Test configuration syntax
sudo nginx -t
# If test passes, reload Nginx
sudo systemctl reload nginx
Step 5: Configure DNS/Hosts File
For local testing, add the domain to your hosts file:
sudo nano /etc/hosts
Add this line:
127.0.0.1 myapp.local www.myapp.local
Now you should be able to visit http://myapp.local
in your browser and see your page.
Advanced Configuration Examples
PHP Application Server Block
For PHP applications (make sure php-fpm is installed), use this configuration:
server {
listen 80;
server_name phpapp.local;
root /var/www/phpapp.local/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
# Cache static files
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Reverse Proxy Server Block
For proxying to Node.js apps or other services:
server {
listen 80;
server_name nodeapp.local;
location / {
proxy_pass http://localhost: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;
proxy_cache_bypass $http_upgrade;
}
}
SSL/HTTPS Configuration
Let’s add SSL support using Let’s Encrypt. First, install Certbot:
sudo apt install certbot python3-certbot-nginx
Obtain and install certificate:
# Replace with your actual domain
sudo certbot --nginx -d myapp.local -d www.myapp.local
Certbot automatically modifies your server block to include SSL configuration. Here’s what a complete HTTPS server block looks like:
server {
listen 80;
server_name myapp.local www.myapp.local;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name myapp.local www.myapp.local;
ssl_certificate /etc/letsencrypt/live/myapp.local/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.local/privkey.pem;
# SSL Security
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
root /var/www/myapp.local/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
Performance Optimization and Best Practices
Here are some performance tweaks that make a real difference:
Enable Gzip Compression
Add this to your server block or main nginx.conf:
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate no_last_modified no_etag auth;
gzip_types
application/atom+xml
application/javascript
application/json
application/ld+json
application/manifest+json
application/rss+xml
application/vnd.geo+json
application/vnd.ms-fontobject
application/x-font-ttf
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/bmp
image/svg+xml
image/x-icon
text/cache-manifest
text/css
text/plain
text/vcard
text/vnd.rim.location.xloc
text/vtt
text/x-component
text/x-cross-domain-policy;
Rate Limiting
Protect your server from abuse:
# Add to nginx.conf http block
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Add to server block
location /login {
limit_req zone=login burst=5 nodelay;
# your location config
}
Common Issues and Troubleshooting
Here’s a troubleshooting checklist for when things go wrong:
Problem | Likely Cause | Solution |
---|---|---|
403 Forbidden | Permission issues or missing index file | Check file permissions (755 for dirs, 644 for files) |
502 Bad Gateway | Upstream service down (PHP-FPM, Node.js app) | Check if backend service is running |
Nginx won’t start | Configuration syntax error | Run sudo nginx -t to check syntax |
Wrong site loading | Server block priority/default issues | Check server_name and default_server directives |
Debug Commands That Save Your Sanity
# Check configuration syntax
sudo nginx -t
# Test and show configuration
sudo nginx -T
# Check what sites are enabled
ls -la /etc/nginx/sites-enabled/
# View error logs
sudo tail -f /var/log/nginx/error.log
# Check if nginx is binding to correct ports
sudo netstat -tlnp | grep nginx
# Reload without stopping (graceful reload)
sudo nginx -s reload
Real-World Use Cases and Examples
Multi-Environment Setup
Here’s how to set up staging and production environments:
# Production - myapp.com
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
root /var/www/production/html;
# SSL and other config
}
# Staging - staging.myapp.com
server {
listen 443 ssl http2;
server_name staging.myapp.com;
root /var/www/staging/html;
# Basic auth for staging
auth_basic "Staging Environment";
auth_basic_user_file /etc/nginx/.htpasswd;
# SSL and other config
}
Load Balancing Multiple App Instances
upstream app_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name myapp.com;
location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Security Considerations
Always implement these security measures:
- Hide Nginx version:
server_tokens off;
- Disable unused HTTP methods
- Set proper security headers
- Use strong SSL/TLS configuration
- Implement rate limiting on sensitive endpoints
- Regularly update Nginx and SSL certificates
# Block common exploit attempts
location ~ /(\.|wp-admin|wp-includes) {
deny all;
}
# Only allow specific HTTP methods
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 405;
}
Performance Benchmarks and Monitoring
With proper server block configuration, you can expect significant performance improvements. Here’s what well-configured Nginx can handle:
Configuration | Concurrent Connections | Requests/Second | Memory Usage |
---|---|---|---|
Basic Static Files | 10,000+ | 50,000+ | ~10MB |
PHP-FPM Proxy | 1,000-5,000 | 1,000-5,000 | ~50MB |
Node.js Reverse Proxy | 5,000-10,000 | 10,000-20,000 | ~30MB |
Monitor your server blocks with:
# Enable status page
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
The beauty of Nginx server blocks is their flexibility and efficiency. Unlike Apache virtual hosts that spawn new processes, Nginx handles everything in a single master process with worker processes, making it incredibly resource-efficient. You can run dozens of sites on a modest server without breaking a sweat.
For more advanced configurations and edge cases, check out the official Nginx documentation. The server names documentation is particularly useful for understanding complex routing scenarios.
Remember: always test your configurations in a staging environment first, keep backups of working configs, and monitor your error logs religiously. Nginx server blocks are powerful, but with great power comes great responsibility to not break production at 3 AM.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.