Install and configure Caddy web server with automatic HTTPS and reverse proxy

Beginner 25 min Apr 03, 2026 22 views
Ubuntu 24.04 Ubuntu 22.04 Debian 12 AlmaLinux 9 Rocky Linux 9 Fedora 41

Set up Caddy, a modern web server with automatic HTTPS certificates from Let's Encrypt, zero-config HTTP/2, and built-in reverse proxy capabilities for production applications.

Prerequisites

  • Root or sudo access
  • Domain name pointed to your server
  • Ports 80 and 443 accessible

What this solves

Caddy is a modern web server that automatically handles HTTPS certificates, HTTP/2, and reverse proxy configuration without complex setup. Unlike traditional web servers that require manual SSL certificate management, Caddy automatically obtains and renews Let's Encrypt certificates, making it perfect for production deployments where you need secure, reliable web hosting with minimal maintenance overhead.

Step-by-step installation

Update system packages

Start by updating your package manager to ensure you have the latest package information and security updates.

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

Install required dependencies

Install curl and other tools needed to add the official Caddy repository and verify package signatures.

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
sudo dnf install -y curl dnf-plugins-core

Add Caddy official repository

Add the official Caddy repository to ensure you get the latest stable version with automatic updates through your package manager.

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo dnf copr enable @caddy/caddy -y

Install Caddy web server

Install the Caddy package which includes the web server binary, systemd service unit, and default configuration structure.

sudo apt install -y caddy
sudo dnf install -y caddy

Configure firewall rules

Open HTTP (80) and HTTPS (443) ports to allow web traffic. Caddy uses port 80 for ACME HTTP-01 challenges and port 443 for HTTPS traffic.

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

Create basic Caddy configuration

Create a basic Caddyfile configuration that serves a simple website with automatic HTTPS. Replace example.com with your actual domain name.

example.com {
    root * /var/www/html
    file_server
    encode gzip
    
    log {
        output file /var/log/caddy/access.log
        format json
    }
    
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        X-XSS-Protection "1; mode=block"
        Referrer-Policy "strict-origin-when-cross-origin"
    }
}

Create web root directory

Create the directory structure for your website files with proper ownership and permissions for the Caddy user.

sudo mkdir -p /var/www/html
sudo mkdir -p /var/log/caddy
sudo chown -R caddy:caddy /var/www/html
sudo chown -R caddy:caddy /var/log/caddy
sudo chmod 755 /var/www/html
sudo chmod 755 /var/log/caddy

Create sample web page

Create a simple HTML page to test your Caddy installation and verify automatic HTTPS is working correctly.




    
    
    Caddy Server - Working!
    


    

Caddy Web Server

✅ Server is running successfully!

Automatic HTTPS is enabled with Let's Encrypt certificates.

HTTP/2 and modern security headers are configured.

sudo chown caddy:caddy /var/www/html/index.html
sudo chmod 644 /var/www/html/index.html

Validate Caddy configuration

Test your Caddyfile syntax before starting the service to catch any configuration errors early.

sudo caddy validate --config /etc/caddy/Caddyfile

Enable and start Caddy service

Enable Caddy to start automatically on boot and start the service immediately. Check the status to ensure it's running correctly.

sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy

Configure reverse proxy

Set up reverse proxy configuration

Configure Caddy to proxy requests to backend applications. This example shows proxying to a Node.js app on port 3000 and an API server on port 8080.

example.com {
    reverse_proxy localhost:3000
    encode gzip
    
    log {
        output file /var/log/caddy/access.log
        format json
    }
    
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        X-XSS-Protection "1; mode=block"
    }
}

api.example.com {
    reverse_proxy localhost:8080 {
        header_up Host {upstream_hostport}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
    encode gzip
    
    log {
        output file /var/log/caddy/api-access.log
        format json
    }
}

Configure load balancing

Set up load balancing across multiple backend servers with health checks and failover capabilities.

app.example.com {
    reverse_proxy {
        to localhost:3000 localhost:3001 localhost:3002
        lb_policy round_robin
        health_uri /health
        health_interval 30s
        health_timeout 5s
    }
    encode gzip
    
    log {
        output file /var/log/caddy/app-access.log
        format json
    }
}

Add path-based routing

Configure different backend services based on URL paths, useful for microservices architectures.

example.com {
    # Serve static files for the main site
    handle /assets/* {
        root * /var/www/html
        file_server
    }
    
    # Proxy API requests to backend
    handle /api/* {
        reverse_proxy localhost:8080
    }
    
    # Proxy admin panel to different service
    handle /admin/* {
        reverse_proxy localhost:9000 {
            header_up X-Forwarded-User {http.request.header.X-User}
        }
    }
    
    # Default handler for main application
    handle {
        reverse_proxy localhost:3000
    }
    
    encode gzip
    log {
        output file /var/log/caddy/access.log
        format json
    }
}

Reload Caddy configuration

Apply the new configuration without downtime using Caddy's graceful reload feature.

sudo systemctl reload caddy
sudo systemctl status caddy

Configure virtual hosts

Set up multiple domains

Configure multiple websites with different domains, each with their own document root and configuration.

# Primary website
example.com, www.example.com {
    root * /var/www/example.com
    file_server
    encode gzip
    
    # Redirect www to non-www
    @www host www.example.com
    redir @www https://example.com{uri} permanent
    
    log {
        output file /var/log/caddy/example.com.log
        format json
    }
}

Blog subdomain

blog.example.com { reverse_proxy localhost:2368 # Ghost blog encode gzip log { output file /var/log/caddy/blog.log format json } }

Development site

dev.example.com { root * /var/www/dev.example.com file_server # Basic auth for development site basicauth { developer $2a$14$hEm9U2CRyCXcZhKRlBF.1OZdHFEKF9TgC7RHdL6Z8qJ4aZeWxXoVy } log { output file /var/log/caddy/dev.log format json } }

Create directory structure

Create separate directories for each virtual host with proper ownership and permissions.

sudo mkdir -p /var/www/example.com
sudo mkdir -p /var/www/dev.example.com
sudo chown -R caddy:caddy /var/www/example.com
sudo chown -R caddy:caddy /var/www/dev.example.com
sudo chmod -R 755 /var/www/example.com
sudo chmod -R 755 /var/www/dev.example.com

Generate basic auth password

Create a hashed password for basic authentication on the development site using Caddy's built-in hash command.

caddy hash-password --plaintext 'your-secure-password'
Note: Replace the hash in the Caddyfile with the output from this command for the basicauth directive.

Performance optimization and monitoring

Configure advanced caching

Set up intelligent caching headers and compression for optimal performance across different content types.

example.com {
    root * /var/www/html
    
    # Cache static assets aggressively
    @static path .css .js .png .jpg .jpeg .gif .ico .svg .woff .woff2 .ttf .eot
    header @static {
        Cache-Control "public, max-age=31536000, immutable"
        Expires "1 year"
    }
    
    # Cache HTML with shorter duration
    @html path *.html
    header @html {
        Cache-Control "public, max-age=3600"
    }
    
    file_server
    encode gzip brotli
    
    # Security headers
    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        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'"
    }
    
    log {
        output file /var/log/caddy/access.log {
            roll_size 100MiB
            roll_keep 5
            roll_keep_for 30d
        }
        format json
        level INFO
    }
}

Set up log rotation

Configure logrotate to manage Caddy log files and prevent disk space issues. This configuration is similar to what you'd use for centralized log management.

/var/log/caddy/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0644 caddy caddy
    postrotate
        systemctl reload caddy
    endscript
}

Configure system resource limits

Optimize Caddy's systemd service for production workloads with appropriate resource limits and security hardening.

[Service]

Resource limits

LimitNOFILE=65536 LimitNPROC=4096

Security hardening

NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ProtectKernelTunables=true ProtectKernelModules=true ProtectControlGroups=true ReadWritePaths=/var/log/caddy /var/lib/caddy

Performance optimization

Environment=CADDY_AGREE=true Environment=CADDY_API=localhost:2019
sudo mkdir -p /etc/systemd/system/caddy.service.d
sudo systemctl daemon-reload
sudo systemctl restart caddy

Enable Caddy admin API

Configure the admin API for monitoring and dynamic configuration updates. Restrict access to localhost for security.

{
    admin localhost:2019
    log default {
        output file /var/log/caddy/caddy.log
        format json
        level INFO
    }
}

example.com {
    # Your site configuration here
    root * /var/www/html
    file_server
    encode gzip
}

Verify your setup

Test your Caddy installation and configuration with these verification commands.

# Check Caddy service status
sudo systemctl status caddy

Verify Caddy version and build info

caddy version

Test configuration syntax

sudo caddy validate --config /etc/caddy/Caddyfile

Check SSL certificate status

caddy list-certificates

Test HTTP to HTTPS redirect

curl -I http://example.com

Test HTTPS with SSL info

curl -I https://example.com

Check admin API (if enabled)

curl http://localhost:2019/config/

Monitor real-time logs

sudo tail -f /var/log/caddy/access.log

Check process and memory usage

sudo ps aux | grep caddy sudo systemctl show caddy --property=MainPID,MemoryCurrent,CPUUsageNSec

Common issues

SymptomCauseFix
Service fails to startConfiguration syntax errorsudo caddy validate --config /etc/caddy/Caddyfile to check syntax
Let's Encrypt certificate failsDomain not pointing to server or port 80 blockedVerify DNS records and firewall rules for ports 80/443
Permission denied on web filesIncorrect file ownershipsudo chown -R caddy:caddy /var/www/html and chmod 644 for files, 755 for directories
Reverse proxy connection refusedBackend service not runningCheck backend service: sudo netstat -tlnp | grep :3000
High memory usageLarge log files or no log rotationConfigure log rotation and check /var/log/caddy/ disk usage
Admin API not accessibleAPI disabled or listening on wrong interfaceAdd admin localhost:2019 to global Caddyfile block
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions like 644 for files and 755 for directories.

Next steps

Automated install script

Run this to automate the entire setup

#caddy #web-server #automatic-https #reverse-proxy #lets-encrypt

Need help?

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.

Talk to an engineer