Why I Created Zero: A Lightweight SSL Certificate Manager
· docker
SSL certificates should be boring infrastructure. You point a tool at a domain, it gets the cert, it renews the cert, you never think about it again. That’s the whole job.
So why does every existing solution make this feel like configuring a space shuttle?
The landscape is wrong
Certbot is the default answer, and it works. But it drags Python into your container, demands you understand its config file hierarchy, and turns “renew a certificate” into an afternoon of debugging when Docker gets involved. For a single domain on a small VPS, it’s a forklift where you need a hand truck.
Traefik handles certs automatically — as a side effect of being a full reverse proxy and load balancer. Adopting Traefik’s entire ecosystem to get free SSL is like buying a restaurant because you wanted lunch.
Caddy is genuinely great. Automatic HTTPS, minimal config, batteries included. But it is the web server. If you’re already running Nginx — and most of us are — Caddy doesn’t slot in. It replaces.
What I wanted was simpler: a single binary that obtains certs, renews them, and tells Nginx to reload. Nothing more.
So I built Zero
Zero is a single-purpose SSL certificate manager written in Go. No Python runtime. No web server bundled in. No ecosystem to adopt.
It does four things:
- Runs an HTTP server on port 80 to handle ACME challenges
- Redirects all other HTTP traffic to HTTPS
- Checks certificates daily and renews them 30 days before expiry
- Executes a post-renewal hook — like reloading Nginx in a sibling container
That’s it. Simplicity is a superpower.
Docker Compose, the real-world version
Here’s how Zero actually runs in production:
volumes:
certs:
services:
zero:
image: yarlson/zero:latest
ports:
- "80:80"
volumes:
- certs:/certs
command:
- -d
- example.com
- -e
- [email protected]
- -c
- /certs
- --hook
- nginx -s reload
- --hook-container
- nginx
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- certs:/etc/nginx/certs:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- zero
restart: unless-stoppedZero gets the cert, writes it to a shared volume, reloads Nginx. Nginx serves HTTPS. Renewals happen silently at 2 AM. You deploy this once and forget about it.
1.2 MiB
Let’s talk about resource usage. Here’s docker stats from a real deployment:
aerie@pdg:~$ docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
4d533325bc1d ftl-flask-demo-zero 0.00% 1.238MiB / 961.6MiB 0.13% 3.44kB / 2.03kB 0B / 0B 5
279fae42a9a6 ftl-flask-demo-proxy 0.00% 4.691MiB / 961.6MiB 0.49% 985kB / 1.06MB 2.86MB / 24.6kB 2
2472f07ebcd4 ftl-flask-demo-flask-app 0.01% 101.7MiB / 961.6MiB 10.57% 3.44MB / 4.21MB 26.3MB / 0B 4
c4ba57ee6afb ftl-flask-demo-postgres 0.01% 25.14MiB / 961.6MiB 2.61% 3.62MB / 2.87MB 15MB / 287kB 9Zero: 1.238 MiB. The Nginx proxy next to it uses nearly four times that. On a cheap VPS with a gig of RAM, this matters. Every megabyte you don’t burn on infrastructure is a megabyte your app can use.
Zero inside FTL
Zero also serves as the SSL backbone of FTL (Faster Than Light), my deployment tool for single-server setups. FTL takes a YAML config, provisions containers, wires up Nginx as a reverse proxy, and hands certificate management entirely to Zero.
project:
name: my-project
domain: my-project.example.com
email: [email protected]
server:
host: my-project.example.com
user: my-project
ssh_key: ~/.ssh/id_rsa
services:
- name: web
path: ./src
port: 80
health_check:
path: /
routes:
- path: /
dependencies:
- "postgres:16"No SSL section. No certificate flags. FTL reads the domain and email, Zero handles the rest. Certificates appear, renewals happen, Nginx reloads — all invisible. That’s the whole point.
The thesis
Look, SSL certificate management is a solved problem that the existing tools insist on making unsolved. Certbot is too heavy. Traefik is too much. Caddy is the wrong shape if you already have a web server.
Zero does one thing well: it gets your certs, renews your certs, and stays out of your way. A single Go binary, a couple megabytes of RAM, zero ongoing attention required.
Sometimes the best tool is the one you stop thinking about the moment you deploy it.
