Configure Lighttpd 1.4 with multiple virtual hosts and subdomains

Intermediate 45 min Apr 15, 2026 769 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up Lighttpd web server with multiple virtual hosts, subdomain routing, SSL certificates, and performance optimization for hosting multiple websites on a single server with security hardening.

Prerequisites

  • Root or sudo access to the server
  • Domain names pointing to your server IP address
  • Basic knowledge of DNS configuration
  • Familiarity with command line operations

What this solves

Virtual hosts allow you to serve multiple websites from a single Lighttpd server, each with its own domain name and configuration. This tutorial shows you how to configure domain-based virtual hosting, subdomain routing, SSL certificates, and security hardening for production environments.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you get the latest versions of all packages.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Lighttpd and required modules

Install Lighttpd web server along with SSL support and additional modules needed for virtual hosts and subdomains.

sudo apt install -y lighttpd lighttpd-mod-openssl openssl certbot
sudo dnf install -y lighttpd lighttpd-mod_openssl openssl certbot

Create directory structure for virtual hosts

Set up organized directory structure for each virtual host with proper ownership and permissions.

sudo mkdir -p /var/www/example.com/{public_html,logs}
sudo mkdir -p /var/www/api.example.com/{public_html,logs}
sudo mkdir -p /var/www/blog.example.com/{public_html,logs}
sudo mkdir -p /var/www/testsite.org/{public_html,logs}

Set proper ownership and permissions

Configure correct file ownership and permissions for web content. The www-data user needs read access to serve files, and the lighttpd process must be able to write to log directories.

sudo chown -R www-data:www-data /var/www/
sudo chmod -R 755 /var/www/*/public_html
sudo chmod -R 775 /var/www/*/logs
Never use chmod 777. It gives every user on the system full access to your files. Instead, use specific ownership with chown and minimal permissions like 755 for directories and 644 for files.

Create sample HTML files for testing

Create simple HTML files to test each virtual host configuration.

sudo tee /var/www/example.com/public_html/index.html > /dev/null << 'EOF'




<h1>Welcome to example.com</h1>
<p>Main website content</p>


EOF

sudo tee /var/www/api.example.com/public_html/index.html > /dev/null << 'EOF'




<h1>API Subdomain</h1>
<p>API endpoint at api.example.com</p>


EOF

sudo tee /var/www/blog.example.com/public_html/index.html > /dev/null << 'EOF'




<h1>Blog Subdomain</h1>
<p>Blog content at blog.example.com</p>


EOF

sudo tee /var/www/testsite.org/public_html/index.html > /dev/null << 'EOF'




<h1>Welcome to testsite.org</h1>
<p>Secondary domain content</p>


EOF

Configure main Lighttpd settings

Backup the original configuration and create a new main configuration file with virtual host support enabled.

sudo cp /etc/lighttpd/lighttpd.conf /etc/lighttpd/lighttpd.conf.backup
server.modules = (
    "mod_access",
    "mod_alias",
    "mod_compress",
    "mod_redirect",
    "mod_rewrite",
    "mod_openssl",
    "mod_accesslog",
    "mod_expire",
    "mod_setenv"
)

# Basic server settings
server.document-root = "/var/www/html"
server.upload-dirs = ("/var/cache/lighttpd/uploads")
server.errorlog = "/var/log/lighttpd/error.log"
server.pid-file = "/var/run/lighttpd.pid"
server.username = "www-data"
server.groupname = "www-data"
server.port = 80

# Performance optimization
server.max-connections = 1024
server.max-fds = 2048
server.network-backend = "linux-sendfile"
server.max-request-size = 16384

# MIME types
include "/etc/lighttpd/mime-types.conf"

# Default index files
index-file.names = ("index.php", "index.html", "index.htm", "default.htm")
url.access-deny = ("~", ".inc")

# Compression
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype = ("application/javascript", "text/css", "text/html", "text/plain")

# Security headers
setenv.add-response-header = (
    "X-Frame-Options" => "SAMEORIGIN",
    "X-Content-Type-Options" => "nosniff",
    "X-XSS-Protection" => "1; mode=block",
    "Referrer-Policy" => "strict-origin-when-cross-origin"
)

# Include virtual host configurations
include "/etc/lighttpd/vhosts.d/*.conf"

Create virtual hosts directory

Create a dedicated directory for virtual host configuration files to keep the main configuration organized.

sudo mkdir -p /etc/lighttpd/vhosts.d

Configure primary domain virtual host

Create the virtual host configuration for the main domain with logging and error handling.

# Main domain: example.com
$HTTP["host"] == "example.com" {
    server.document-root = "/var/www/example.com/public_html"
    accesslog.filename = "/var/www/example.com/logs/access.log"
    server.errorlog = "/var/www/example.com/logs/error.log"
    
    # Custom error pages
    server.error-handler-404 = "/404.html"
    
    # Cache control for static assets
    $HTTP["url"] =~ "\.(css|js|png|jpg|jpeg|gif|ico|svg)$" {
        expire.url = ("" => "access plus 1 months")
    }
}

# Redirect www to non-www
$HTTP["host"] == "www.example.com" {
    url.redirect = ("^/(.*)" => "https://example.com/$1")
}

Configure subdomain virtual hosts

Set up virtual host configurations for API and blog subdomains with specific optimizations for each use case.

# API subdomain: api.example.com
$HTTP["host"] == "api.example.com" {
    server.document-root = "/var/www/api.example.com/public_html"
    accesslog.filename = "/var/www/api.example.com/logs/access.log"
    server.errorlog = "/var/www/api.example.com/logs/error.log"
    
    # API-specific headers
    setenv.add-response-header = (
        "Access-Control-Allow-Origin" => "https://example.com",
        "Access-Control-Allow-Methods" => "GET, POST, PUT, DELETE, OPTIONS",
        "Access-Control-Allow-Headers" => "Content-Type, Authorization",
        "Content-Type" => "application/json; charset=utf-8"
    )
    
    # No caching for API responses
    setenv.add-response-header += (
        "Cache-Control" => "no-cache, no-store, must-revalidate",
        "Pragma" => "no-cache",
        "Expires" => "0"
    )
}

# Blog subdomain: blog.example.com
$HTTP["host"] == "blog.example.com" {
    server.document-root = "/var/www/blog.example.com/public_html"
    accesslog.filename = "/var/www/blog.example.com/logs/access.log"
    server.errorlog = "/var/www/blog.example.com/logs/error.log"
    
    # Blog-optimized caching
    $HTTP["url"] =~ "\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2)$" {
        expire.url = ("" => "access plus 3 months")
    }
    
    # HTML caching for blog posts
    $HTTP["url"] =~ "\.html$" {
        expire.url = ("" => "access plus 1 hours")
    }
}

Configure secondary domain

Set up a completely separate domain with its own virtual host configuration.

# Secondary domain: testsite.org
$HTTP["host"] == "testsite.org" {
    server.document-root = "/var/www/testsite.org/public_html"
    accesslog.filename = "/var/www/testsite.org/logs/access.log"
    server.errorlog = "/var/www/testsite.org/logs/error.log"
    
    # Different security headers for this domain
    setenv.add-response-header = (
        "X-Frame-Options" => "DENY",
        "Strict-Transport-Security" => "max-age=31536000; includeSubDomains"
    )
}

# Handle www redirect for secondary domain
$HTTP["host"] == "www.testsite.org" {
    url.redirect = ("^/(.*)" => "https://testsite.org/$1")
}

Configure SSL certificates

Set up SSL certificates using Let's Encrypt for secure HTTPS connections. First, test the configuration and start Lighttpd temporarily.

sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf
sudo systemctl start lighttpd

Obtain SSL certificates for all domains and subdomains:

sudo certbot certonly --webroot -w /var/www/example.com/public_html -d example.com
sudo certbot certonly --webroot -w /var/www/api.example.com/public_html -d api.example.com
sudo certbot certonly --webroot -w /var/www/blog.example.com/public_html -d blog.example.com
sudo certbot certonly --webroot -w /var/www/testsite.org/public_html -d testsite.org

Configure SSL virtual hosts

Create SSL-enabled virtual host configurations with HTTPS redirects and security headers.

# SSL Configuration
$SERVER["socket"] == ":443" {
    ssl.engine = "enable"
    ssl.pemfile = "/etc/letsencrypt/live/example.com/privkey.pem"
    ssl.ca-file = "/etc/letsencrypt/live/example.com/fullchain.pem"
    
    # Modern SSL configuration
    ssl.use-sslv2 = "disable"
    ssl.use-sslv3 = "disable"
    ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
    ssl.honor-cipher-order = "enable"
    
    # example.com SSL
    $HTTP["host"] == "example.com" {
        ssl.pemfile = "/etc/letsencrypt/live/example.com/privkey.pem"
        ssl.ca-file = "/etc/letsencrypt/live/example.com/fullchain.pem"
        server.document-root = "/var/www/example.com/public_html"
        accesslog.filename = "/var/www/example.com/logs/ssl_access.log"
        
        setenv.add-response-header = (
            "Strict-Transport-Security" => "max-age=31536000; includeSubDomains; preload"
        )
    }
    
    # API subdomain SSL
    $HTTP["host"] == "api.example.com" {
        ssl.pemfile = "/etc/letsencrypt/live/api.example.com/privkey.pem"
        ssl.ca-file = "/etc/letsencrypt/live/api.example.com/fullchain.pem"
        server.document-root = "/var/www/api.example.com/public_html"
        accesslog.filename = "/var/www/api.example.com/logs/ssl_access.log"
    }
    
    # Blog subdomain SSL
    $HTTP["host"] == "blog.example.com" {
        ssl.pemfile = "/etc/letsencrypt/live/blog.example.com/privkey.pem"
        ssl.ca-file = "/etc/letsencrypt/live/blog.example.com/fullchain.pem"
        server.document-root = "/var/www/blog.example.com/public_html"
        accesslog.filename = "/var/www/blog.example.com/logs/ssl_access.log"
    }
    
    # Secondary domain SSL
    $HTTP["host"] == "testsite.org" {
        ssl.pemfile = "/etc/letsencrypt/live/testsite.org/privkey.pem"
        ssl.ca-file = "/etc/letsencrypt/live/testsite.org/fullchain.pem"
        server.document-root = "/var/www/testsite.org/public_html"
        accesslog.filename = "/var/www/testsite.org/logs/ssl_access.log"
    }
}

# Force HTTPS redirects
$HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
        url.redirect = (".*" => "https://%0$0")
    }
}

Configure automatic certificate renewal

Set up automatic SSL certificate renewal with Lighttpd reload to prevent service interruption.

sudo crontab -e

Add this line to the crontab:

0 2 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload lighttpd"

Configure log rotation

Set up log rotation to prevent log files from consuming too much disk space.

/var/www/*/logs/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    postrotate
        if systemctl is-active lighttpd > /dev/null; then
            systemctl reload lighttpd
        fi
    endscript
}

Enable and start Lighttpd

Test the configuration and enable the service to start automatically on boot.

sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf
sudo systemctl enable lighttpd
sudo systemctl restart lighttpd
sudo systemctl status lighttpd

Configure firewall rules

Open the necessary ports for HTTP and HTTPS traffic through the firewall.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Verify your setup

Test each virtual host to ensure proper configuration and functionality.

# Check Lighttpd configuration
sudo lighttpd -t -f /etc/lighttpd/lighttpd.conf

# Verify service status
sudo systemctl status lighttpd

# Test HTTP responses (replace with your actual domains)
curl -H "Host: example.com" http://localhost/
curl -H "Host: api.example.com" http://localhost/
curl -H "Host: blog.example.com" http://localhost/
curl -H "Host: testsite.org" http://localhost/

# Test HTTPS redirects
curl -I -H "Host: example.com" http://localhost/

# Check SSL certificate status
sudo certbot certificates

# Verify log files are being created
sudo ls -la /var/www/*/logs/

# Test log rotation configuration
sudo logrotate -d /etc/logrotate.d/lighttpd-vhosts

Performance optimization

Enable advanced caching

Configure advanced caching strategies for better performance across all virtual hosts.

# Advanced caching configuration
$HTTP["url"] =~ "\.(css|js)$" {
    expire.url = ("" => "access plus 1 months")
    setenv.add-response-header = ("Cache-Control" => "public, max-age=2592000")
}

$HTTP["url"] =~ "\.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$" {
    expire.url = ("" => "access plus 6 months")
    setenv.add-response-header = ("Cache-Control" => "public, max-age=15552000")
}

# Enable compression for text files
compress.filetype = (
    "text/plain",
    "text/html",
    "text/css",
    "text/javascript",
    "application/javascript",
    "application/json",
    "application/xml",
    "text/xml"
)

Enable the caching configuration:

sudo ln -s /etc/lighttpd/conf-available/10-cache.conf /etc/lighttpd/conf-enabled/
sudo systemctl reload lighttpd

Optimize worker processes

Configure Lighttpd for optimal performance based on server resources. You can integrate this with Redis caching for even better performance.

# Performance tuning
server.max-connections = 2048
server.max-fds = 4096
server.max-request-size = 8192

# Connection handling
server.max-keep-alive-requests = 100
server.max-keep-alive-idle = 30
server.max-read-idle = 60
server.max-write-idle = 360

# Network optimization
server.network-backend = "linux-sendfile"
server.upload-dirs = ("/tmp")

# Memory limits
server.max-request-field-size = 8192
server.max-request-fields = 100
sudo ln -s /etc/lighttpd/conf-available/15-performance.conf /etc/lighttpd/conf-enabled/
sudo systemctl reload lighttpd

Security hardening

Configure additional security headers

Add comprehensive security headers to protect against common web attacks.

# Security headers for all virtual hosts
setenv.add-response-header = (
    "X-Frame-Options" => "SAMEORIGIN",
    "X-Content-Type-Options" => "nosniff",
    "X-XSS-Protection" => "1; mode=block",
    "Referrer-Policy" => "strict-origin-when-cross-origin",
    "Content-Security-Policy" => "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
    "Permissions-Policy" => "geolocation=(), microphone=(), camera=()"
)

# Hide server information
server.tag = ""

# Disable server status module
server.modules -= ("mod_status")

# Access restrictions
$HTTP["url"] =~ "^/(\.|_)" {
    url.access-deny = ("")
}

# Block common attack patterns
$HTTP["url"] =~ "(\.php$|\.asp$|\.aspx$)" {
    $HTTP["host"] !~ "^(api\.)" {
        url.access-deny = ("")
    }
}

# Rate limiting simulation with mod_rewrite
$HTTP["remoteip"] =~ ".*" {
    var.ratelimit = 1
}
sudo ln -s /etc/lighttpd/conf-available/20-security.conf /etc/lighttpd/conf-enabled/
sudo systemctl reload lighttpd

Common issues

SymptomCauseFix
Virtual host serves default pageHost header not matching exactlyCheck host configuration

Automated install script

Run this to automate the entire setup

Nie chcesz zarządzać tym samodzielnie?

Zarządzamy infrastrukturą firm, które zależą od dostępności. W pełni zarządzana, z jednym stałym kontaktem, który zna Twoje środowisko.

Macie jednego stałego opiekuna, który zna Waszą konfigurację

Rotterdam 04:07 · dostępny w wiadomości, bez formularza zgłoszeń