How to Install and Configure Caddy Web Server on RHEL 7
Caddy is a modern, open-source web server written in Go that sets itself apart from traditional servers like Apache and Nginx through its automatic HTTPS provisioning, minimal configuration syntax, and zero-dependency binary distribution. On RHEL 7, where the standard repositories do not include Caddy, installation requires downloading the binary directly from GitHub releases and integrating it with systemd. This tutorial walks through the complete process: downloading and installing the Caddy binary, writing a Caddyfile, enabling automatic TLS via Let’s Encrypt, configuring reverse proxy and file server directives, adding basic authentication, and comparing the experience against Nginx.
Prerequisites
- RHEL 7 server with a non-root user holding
sudoprivileges - A fully qualified domain name (FQDN) pointed at your server’s public IP (required for automatic HTTPS)
- Ports 80 and 443 open in firewalld
- Internet access to reach GitHub releases and Let’s Encrypt ACME endpoints
curlorwgetinstalled (sudo yum install -y curl)
Step 1: Download the Caddy Binary from GitHub Releases
Caddy is distributed as a single statically compiled binary. Visit https://github.com/caddyserver/caddy/releases to identify the latest stable release, then download the Linux amd64 tarball.
# Check the latest release tag (e.g., v2.8.4) and download
CADDY_VERSION="2.8.4"
curl -L "https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_amd64.tar.gz"
-o /tmp/caddy.tar.gz
# Extract the binary
tar -xzf /tmp/caddy.tar.gz -C /tmp caddy
# Install to a system-wide location
sudo install -o root -g root -m 0755 /tmp/caddy /usr/local/bin/caddy
# Verify the installation
caddy version
The install command copies the binary while setting proper ownership and permissions in a single step, which is safer than a bare cp followed by chmod.
Step 2: Create the caddy System User and Required Directories
Running Caddy as a dedicated non-privileged user limits the blast radius of any potential vulnerability. Caddy also needs directories for its configuration, data (including TLS certificates), and web root files.
# Create a system user with no login shell and no home directory
sudo groupadd --system caddy
sudo useradd --system
--gid caddy
--create-home
--home-dir /var/lib/caddy
--shell /sbin/nologin
--comment "Caddy web server"
caddy
# Create configuration and data directories
sudo mkdir -p /etc/caddy
sudo mkdir -p /var/lib/caddy/.local/share/caddy
sudo mkdir -p /var/www/html
# Set ownership
sudo chown -R caddy:caddy /etc/caddy /var/lib/caddy /var/www/html
Step 3: Allow Caddy to Bind to Privileged Ports
Ports below 1024 are privileged on Linux. Rather than running Caddy as root, grant the binary the CAP_NET_BIND_SERVICE capability so it can bind to ports 80 and 443 as the caddy user.
sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/caddy
# Verify the capability was set
getcap /usr/local/bin/caddy
# Expected output: /usr/local/bin/caddy cap_net_bind_service=eip
Step 4: Write the Caddyfile
The Caddyfile is Caddy’s primary configuration file. Its syntax is intentionally terse. Create /etc/caddy/Caddyfile with a configuration that demonstrates static file serving, a reverse proxy block, and basic authentication.
sudo tee /etc/caddy/Caddyfile << 'EOF'
# Global options block
{
email [email protected]
}
# Site block — Caddy automatically provisions a TLS certificate
example.com {
# Serve static files from /var/www/html
root * /var/www/html
file_server
# Reverse proxy /api/* requests to a local application on port 8080
reverse_proxy /api/* localhost:8080
# Protect the /admin path with basic authentication
basicauth /admin {
# Generate hash: caddy hash-password --plaintext "secretpassword"
admin $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG
}
# Enable access logging
log {
output file /var/log/caddy/access.log
format json
}
}
# HTTP-only block to redirect all traffic to HTTPS
http://example.com {
redir https://{host}{uri} permanent
}
EOF
Create the log directory and set permissions:
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
Step 5: Install Caddy as a systemd Service
Create a systemd unit file so that Caddy starts automatically on boot and can be managed with systemctl.
sudo tee /etc/systemd/system/caddy.service << 'EOF'
[Unit]
Description=Caddy HTTP/2 web server
Documentation=https://caddyserver.com/docs/
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy
Step 6: Configure Firewalld to Allow HTTP and HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# Verify the rules
sudo firewall-cmd --list-services
Step 7: Automatic HTTPS Explained
When Caddy detects a domain name as a site address (rather than an IP or localhost), it automatically performs an ACME challenge against Let’s Encrypt. The process is:
- Caddy requests a certificate from Let’s Encrypt’s ACME endpoint.
- Let’s Encrypt issues an HTTP-01 challenge by requesting
http://yourdomain/.well-known/acme-challenge/<token>. - Caddy serves the token response automatically — no manual DNS or file placement needed.
- The certificate is stored in
/var/lib/caddy/.local/share/caddy/certificates/and renewed automatically before expiry.
# Check certificate storage
sudo ls -la /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/
Step 8: Reverse Proxy Configuration in Depth
Caddy’s reverse_proxy directive supports upstream health checks, load balancing, header manipulation, and transport options. A more advanced reverse proxy block looks like:
example.com {
reverse_proxy /app/* localhost:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
health_uri /healthz
health_interval 10s
health_timeout 5s
lb_policy round_robin
}
}
Step 9: Generating a Basic Auth Password Hash
# Caddy provides a built-in utility for hashing passwords
caddy hash-password --plaintext "mysecurepassword"
# Copy the output hash into the Caddyfile basicauth block, then reload
sudo systemctl reload caddy
Step 10: Comparing Caddy with Nginx
The key differences between Caddy and Nginx on RHEL 7:
- TLS: Caddy handles certificate acquisition and renewal automatically; Nginx requires Certbot or manual certificate management.
- Configuration: Caddyfile syntax is significantly more concise; an equivalent Nginx config is typically 3–5× longer.
- HTTP/2 and HTTP/3: Caddy enables these by default; Nginx requires explicit directives and HTTP/3 requires patched builds on RHEL 7.
- Package availability: Nginx is available in EPEL and base repos for RHEL 7; Caddy requires a manual binary download.
- Performance: For most workloads, both are comparable. Nginx has a slight edge under extremely high concurrency due to its event-driven C architecture.
Troubleshooting Common Issues
# View Caddy logs via journald
sudo journalctl -u caddy -f
# Test the Caddyfile syntax without restarting
caddy validate --config /etc/caddy/Caddyfile
# Reload configuration without downtime
sudo systemctl reload caddy
# Check which port Caddy is listening on
sudo ss -tlnp | grep caddy
Caddy’s combination of automatic HTTPS, clean Caddyfile syntax, and single-binary deployment makes it a compelling alternative to Nginx for teams that want to reduce operational overhead around TLS certificate management. On RHEL 7, the slightly manual installation process is a one-time cost that pays dividends in reduced maintenance work compared to managing certificate renewals with Certbot on Nginx. For projects that require the extensive third-party module ecosystem of Nginx or Apache, those servers remain the better choice — but for straightforward reverse proxy, static serving, and API gateway use cases, Caddy is well worth considering.