This episode secures transport: manual HTTPS with http.Server, automatic Let's Encrypt certificates via autocert and ACME, and HTTP/2. You will also set up a reverse proxy with httputil.ReverseProxy on a subrouter and integrate the application with nginx and Caddy.

Production applications almost always sit behind HTTPS and a proxy. Episode 15 makes sure you understand both from the Go side: enabling TLS directly in http.Server, getting automatic certificates from Let's Encrypt, using HTTP/2, and placing the chi router behind a reverse proxy — or acting as a reverse proxy itself.
Understanding this layer matters for real debugging: expired certificates, proxies that alter headers, and inactive HTTP/2 are common problems that are often misdiagnosed.
The simplest mode: provide a certificate and a private key:
openssl req -x509 -newkey rsa:2048 -keyout key.pem \
-out cert.pem -days 365 -nodes -subj "/CN=localhost"openssl req -x509 -newkey rsa:2048 creates a self-signed certificate for development. In production, certificates must be issued by a trusted CA.
srv := &http.Server{
Addr: ":443",
Handler: Routes(deps),
TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12},
}
srv.ListenAndServeTLS("cert.pem", "key.pem")ListenAndServeTLS("cert.pem", "key.pem") enables HTTPS with the provided certificate. TLSConfig.MinVersion restricts to TLS 1.2 and above, rejecting insecure legacy protocols.
autocert from the Go ecosystem handles ACME end to end:
m := &autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("api.example.com"),
Cache: autocert.DirCache("certs"),
}
srv := &http.Server{
Addr: ":https",
TLSConfig: m.TLSConfig(),
Handler: Routes(deps),
}
go func() {
http.ListenAndServe(":http", m.HTTPHandler(nil))
}()
srv.ListenAndServeTLS("", "")autocert.Manager{Prompt: autocert.AcceptTOS, HostPolicy: ...} issues and renews Let's Encrypt certificates automatically. m.HTTPHandler(nil) serves the ACME challenge on port 80 for domain verification.
DirCache stores certificates so they aren't re-requested constantly.Good news: http.Server enables HTTP/2 automatically when TLS is in use. No extra configuration needed.
curl --http2 -I https://api.example.comcurl --http2 -I https://api.example.com shows HTTP/2 200 in the response when both the server and the proxy support it. HTTP/2 multiplexing means many parallel requests no longer queue on a single connection.
Behind nginx or Caddy, HTTP/2 is usually terminated at the proxy — the Go app speaks HTTP/1.1 to the proxy, and the proxy speaks HTTP/2 to the client. This is normal and not a problem.
chi can become a gateway: mount a reverse proxy on a specific path:
backend := httputil.NewSingleHostReverseProxy(
&url.URL{Scheme: "http", Host: "localhost:9000"})
r.Mount("/internal", backend)httputil.NewSingleHostReverseProxy(...) forwards requests under /internal/* to the localhost:9000 service. This is the basic microservices pattern: one entrypoint, many backends.
To change the path as it's forwarded:
proxy := httputil.NewReverseProxy(&httputil.Director{
Dir: func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = "backend:9000"
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/internal")
},
})strings.TrimPrefix(req.URL.Path, "/internal") strips the prefix before forwarding — the client sees /internal/health, the backend receives /health.
A classic nginx configuration in front of the app:
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
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-For $proxy_add_x_forwarded_for appends the real client IP. Remember: with this header, the RealIP middleware from episode 14 becomes mandatory.
Caddy handles TLS automatically without certificate configuration:
api.example.com {
reverse_proxy 127.0.0.1:8080
}reverse_proxy 127.0.0.1:8080 forwards traffic, and Caddy automatically requests and renews certificates from Let's Encrypt.
Key takeaways:
ListenAndServeTLS with cert and key.httputil.NewSingleHostReverseProxy turns chi into a gateway.middleware.RealIP.In the next episode 16 we go beyond REST: WebSocket, gRPC-Gateway, and more — realtime with gorilla/websocket, Server-Sent Events, mounting non-REST handlers like gRPC-gateway and GraphQL, and building mixed services on a single http.Server.