Welcome to Part 3 of our comprehensive NGINX on Ubuntu series! In our previous posts, we installed NGINX and explored its configuration structure. Now it’s time for the exciting part: setting up virtual hosts to serve multiple websites from a single server and optimizing static content delivery.
What Are Virtual Hosts?
Virtual hosts (also called server blocks in NGINX terminology) allow you to run multiple websites on a single server. Each virtual host can have its own domain name, document root, and configuration settings. This is essential for hosting multiple websites cost-effectively.
graph TD A[Client Requests] --> B[NGINX Server :80] B --> C{Server Name Matching} C -->|example.com| D[Virtual Host 1/var/www/example.com] C -->|blog.example.com| E[Virtual Host 2/var/www/blog] C -->|api.example.com| F[Virtual Host 3/var/www/api] C -->|No Match| G[Default Virtual Host/var/www/html] D --> H[Serve Static Files] E --> I[Serve Blog Content] F --> J[Proxy to Backend API] G --> K[Default Page] style B fill:#e1f5fe style C fill:#fff3e0 style D fill:#e8f5e8 style E fill:#e8f5e8 style F fill:#e8f5e8 style G fill:#ffebee
Setting Up Your First Virtual Host
Let’s create a complete virtual host from scratch. We’ll build a static website for “example.com”.
Step 1: Create Directory Structure
# Create directory for our main website
sudo mkdir -p /var/www/example.com/{public_html,logs}
# Create subdirectories for organization
sudo mkdir -p /var/www/example.com/public_html/{css,js,images}
# Set proper ownership
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com
Step 2: Create Website Content
# Create main HTML file
sudo nano /var/www/example.com/public_html/index.html
Add this content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Example.com - NGINX Virtual Host Demo</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<nav>
<h1>Example.com</h1>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about.html">About</a></li>
</ul>
</nav>
</header>
<main>
<section class="hero">
<h2>Welcome to Our NGINX-Powered Website</h2>
<p>This site is served by NGINX virtual host configuration.</p>
</section>
<section class="features">
<h3>Why NGINX Virtual Hosts?</h3>
<div class="feature-grid">
<div class="feature">
<h4>High Performance</h4>
<p>Efficiently serves static content</p>
</div>
<div class="feature">
<h4>Multiple Sites</h4>
<p>Host multiple domains on one server</p>
</div>
</div>
</section>
</main>
<footer>
<p>© 2025 Example.com - Powered by NGINX on Ubuntu</p>
</footer>
<script src="/js/main.js"></script>
</body>
</html>
Step 3: Create CSS and JavaScript
# Create CSS file
sudo nano /var/www/example.com/public_html/css/style.css
/* Modern CSS for our NGINX demo site */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
header {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 1rem 0;
}
nav {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 2rem;
}
nav h1 {
color: white;
font-size: 2rem;
}
nav ul {
list-style: none;
display: flex;
gap: 2rem;
}
nav a {
color: white;
text-decoration: none;
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 4rem 2rem;
}
.hero {
text-align: center;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 4rem 2rem;
border-radius: 20px;
margin-bottom: 4rem;
}
.hero h2 {
color: white;
font-size: 3rem;
margin-bottom: 1rem;
}
.hero p {
color: rgba(255, 255, 255, 0.9);
font-size: 1.2rem;
}
.features {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 3rem 2rem;
border-radius: 20px;
}
.features h3 {
color: white;
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.feature {
background: rgba(255, 255, 255, 0.1);
padding: 2rem;
border-radius: 15px;
text-align: center;
}
.feature h4 {
color: white;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.feature p {
color: rgba(255, 255, 255, 0.8);
}
footer {
background: rgba(0, 0, 0, 0.3);
color: white;
text-align: center;
padding: 2rem;
margin-top: 4rem;
}
# Create JavaScript file
sudo nano /var/www/example.com/public_html/js/main.js
// Main JavaScript for NGINX demo site
document.addEventListener('DOMContentLoaded', function() {
console.log('NGINX Virtual Host Demo Site Loaded');
// Display current time
function updateTime() {
const now = new Date();
const timeString = now.toLocaleTimeString();
console.log('Site loaded at:', timeString);
}
updateTime();
});
Creating the Virtual Host Configuration
# Create virtual host configuration
sudo nano /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
# Server name - replace with your actual domain
server_name example.com www.example.com;
# Document root
root /var/www/example.com/public_html;
# Index files
index index.html index.htm;
# Main location block
location / {
try_files $uri $uri/ =404;
}
# Optimize static file serving
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
# Enable gzip for text files
gzip on;
gzip_vary on;
gzip_types
text/css
text/javascript
application/javascript;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Hide server version
server_tokens off;
# Custom error pages
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Custom logging
access_log /var/www/example.com/logs/access.log;
error_log /var/www/example.com/logs/error.log;
}
Setting Up a Second Virtual Host
Let’s create a portfolio subdomain to demonstrate multiple virtual hosts:
# Create portfolio directory structure
sudo mkdir -p /var/www/portfolio/{public_html,logs}
sudo chown -R www-data:www-data /var/www/portfolio
# Create portfolio homepage
sudo nano /var/www/portfolio/public_html/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portfolio - NGINX Virtual Host Demo</title>
<style>
body {
font-family: Arial, sans-serif;
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
text-align: center;
padding: 4rem 2rem;
min-height: 100vh;
margin: 0;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
padding: 3rem;
border-radius: 20px;
}
h1 { font-size: 3rem; margin-bottom: 1rem; }
p { font-size: 1.2rem; margin-bottom: 1rem; }
</style>
</head>
<body>
<div class="container">
<h1>Portfolio Site</h1>
<p>This is a separate virtual host running on the same NGINX server.</p>
<p><strong>Virtual Host:</strong> portfolio.example.com</p>
<p><strong>Document Root:</strong> /var/www/portfolio/public_html</p>
</div>
</body>
</html>
# Create portfolio virtual host configuration
sudo nano /etc/nginx/sites-available/portfolio.example.com
server {
listen 80;
listen [::]:80;
server_name portfolio.example.com;
root /var/www/portfolio/public_html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# Static file optimization
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 6M;
add_header Cache-Control "public, immutable";
access_log off;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# Custom logging
access_log /var/www/portfolio/logs/access.log;
error_log /var/www/portfolio/logs/error.log;
}
Enabling and Testing Virtual Hosts
# Enable both virtual hosts
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/portfolio.example.com /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload NGINX if test passes
sudo systemctl reload nginx
# Check enabled sites
ls -la /etc/nginx/sites-enabled/
Static Content Optimization
graph TD A[Client Request] --> B[NGINX Server] B --> C{File Type Check} C -->|HTML| D[Basic Caching1 hour] C -->|CSS/JS| E[Medium Caching1 week] C -->|Images| F[Long Caching1 year] D --> G[Gzip Compression] E --> G F --> H[No CompressionAlready Optimized] G --> I[Security Headers] H --> I I --> J[Serve to Client] style A fill:#e1f5fe style C fill:#fff3e0 style G fill:#e8f5e8 style H fill:#e8f5e8 style I fill:#ffebee
Advanced Static Content Configuration
# Create a reusable snippet for static content optimization
sudo nano /etc/nginx/snippets/static-content.conf
# Static content optimization snippet
# Images - long cache
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Fonts - long cache
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Access-Control-Allow-Origin "*";
access_log off;
}
# CSS and JavaScript - medium cache
location ~* \.(css|js)$ {
expires 1w;
add_header Cache-Control "public";
gzip on;
gzip_vary on;
gzip_types
text/css
text/javascript
application/javascript;
}
# HTML files - short cache
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
gzip on;
gzip_types text/html;
}
Testing Your Virtual Hosts
For testing without DNS, update your hosts file:
# Edit hosts file for local testing
sudo nano /etc/hosts
Add these lines:
127.0.0.1 example.com www.example.com
127.0.0.1 portfolio.example.com
# Test your virtual hosts
curl -H "Host: example.com" http://localhost/
curl -H "Host: portfolio.example.com" http://localhost/
# Check static file serving
curl -H "Host: example.com" http://localhost/css/style.css
Performance Monitoring
# Monitor access logs in real-time
sudo tail -f /var/www/example.com/logs/access.log
# Check for errors
sudo tail -f /var/www/example.com/logs/error.log
# Test gzip compression
curl -H "Accept-Encoding: gzip" -H "Host: example.com" http://localhost/css/style.css -v
Virtual Host Best Practices
1. Organized Directory Structure
/var/www/
├── example.com/
│ ├── public_html/
│ ├── logs/
│ └── backup/
├── portfolio.example.com/
│ ├── public_html/
│ └── logs/
└── default/
2. Security Considerations
# Set proper file permissions
sudo find /var/www/ -type d -exec chmod 755 {} \;
sudo find /var/www/ -type f -exec chmod 644 {} \;
# Ensure correct ownership
sudo chown -R www-data:www-data /var/www/
3. Error Page Customization
# Create custom 404 page
sudo nano /var/www/example.com/public_html/404.html
<!DOCTYPE html>
<html>
<head>
<title>Page Not Found - Example.com</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="hero">
<h2>404 - Page Not Found</h2>
<p>The page you're looking for doesn't exist.</p>
<a href="/">Return Home</a>
</div>
</body>
</html>
Troubleshooting Common Issues
Virtual Host Not Working
# Check if site is enabled
ls -la /etc/nginx/sites-enabled/
# Verify configuration syntax
sudo nginx -t
# Check server_name matching
sudo nginx -T | grep server_name
Static Files Not Loading
# Check file permissions
ls -la /var/www/example.com/public_html/css/
# Verify file ownership
sudo chown -R www-data:www-data /var/www/example.com/
What’s Next?
Congratulations! You’ve successfully set up virtual hosts and optimized static content delivery. You now have multiple websites running on a single NGINX server with proper caching and security headers.
Coming up in Part 4: NGINX + PHP-FPM Setup for Dynamic Content
References and Further Reading
- NGINX Server Names Documentation
- try_files Directive Guide
- NGINX Gzip Module Documentation
- HTTP Cache-Control Headers
This is Part 3 of our 22-part series “The Complete NGINX on Ubuntu Guide.” You’re making great progress! Next, we’ll explore dynamic content with PHP-FPM. Have questions about virtual hosts? Share them in the comments!