The most boring thing about setting up a web server on a new cloud server for me is the initial configuration. From installing(opens new window) and configuring nginx(opens new window) to setting up Let's Encrypt with certbot(opens new window)[1]. There are so many steps involved and so much boilerplate code in the config[2].
This has changed now that Caddy exists. It is an open source web server written in Go that has built-in Let's Encrypt support. And it has the simplest config file format I've ever seen for a web server:
Before: nginx.conf
# HTTP -> HTTPS
server {
listen 80;
listen [::]:80;
server_name vps.dt.in.th;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://vps.dt.in.th$request_uri;
}
}
# HTTPS server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name vps.dt.in.th;
root /var/www/html;
ssl_certificate /etc/letsencrypt/live/vps.dt.in.th/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vps.dt.in.th/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/vps.dt.in.th/chain.pem;
location /api {
proxy_pass http://api:8080/api;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
→
After: Caddyfile
# ingress/Caddyfile
vps.dt.in.th
root * /var/www/html
file_server
reverse_proxy /api/* api:8080
Built with Go, it is available as a standalone binary(opens new window). Packages for popular package managers(opens new window) are available. And there is also a ready-to-use Docker image(opens new window).
# Usage with Docker Compose
Here's a basic docker-compose file that runs Caddy:
# ingress/docker-compose.yml
version: "3.7"
services:
caddy:
image: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./public:/var/www/html
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
Now when I run docker-compose up -d
, Caddy starts up and:
- Listens to port 80 and 443.
- Obtains a Let's Encrypt certificate for domain
vps.dt.in.th
. - Sets up HTTP to HTTPS redirection on port 80.
- Serves files from
public
folder (mounted to/var/www/html
inside container). - Proxies requests to
/api/
to theapi
hostname.
# Configuring Docker networks for reverse proxy
Now, other projects I run on this VPS have their own docker-compose
file. We need to make the container join Caddy's Docker network for Caddy to be able to look it up via hostname.
This can be done by joining an external network and setting a network alias for that network.
# api/docker-compose.yml
version: '3.8'
services:
server:
restart: unless-stopped
build: .
networks:
default:
ingress:
aliases:
- api
networks:
ingress:
external: true
name: ingress_default
A commenter(opens new window) recommended me try out acme.sh(opens new window) should I need to use nginx again. ↩︎
Boilerplate include but not limited to:
- Creating a file in
sites-available
. - Setting up a server block.
- Setting up redirect from HTTP to HTTPS.
- Setting up the path to the SSL certificate file.
- Setting up HTTP headers for the reverse proxy, such as
Host
,X-Real-IP
,X-Forwarded-For
. - Setting up WebSocket support.
- Symlinking the file to
sites-enabled
.
- Creating a file in