The Complete NGINX on Ubuntu Series: Part 3 – Setting Up Virtual Hosts and Serving Static Content

The Complete NGINX on Ubuntu Series: Part 3 – Setting Up Virtual Hosts and Serving Static Content

This entry is part 3 of 5 in the series The Complete NGINX on Ubuntu Series

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>&copy; 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


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!

Navigate<< The Complete NGINX on Ubuntu Series: Part 2 – Understanding Configuration Files and Directory StructureThe Complete NGINX on Ubuntu Series: Part 4A – PHP-FPM Installation and Setup >>

Written by:

339 Posts

View All Posts
Follow Me :