HTTPS for free in Go, with a little help from Let's Encrypt

HTTPS for free in Go, with a little help from Let's Encrypt

part of Go Cookbook
HTTPS encrypts traffic and authenticates the server, protecting users against eavesdropping and tampering. Browsers also require a secure context for many web APIs.
Let’s Encrypt issues free certificates through the ACME protocol. A client can obtain and renew them automatically after proving control of a domain.

Let a reverse proxy manage certificates

For a small Go service, Caddy can manage HTTPS and proxy requests to a Go server listening on a loopback address such as 127.0.0.1:8080.
You can also use a hosted proxy. With Cloudflare, configure Full (strict) and a valid origin certificate so that the connection from Cloudflare to your server is encrypted and verified too. Flexible mode leaves that connection unencrypted.

Support HTTPS directly in Go

The autocert package can handle certificates inside your Go process. Add it to your module with:
go get golang.org/x/crypto/acme/autocert
This complete example serves HTTPS on port 443, answers ACME HTTP challenges on port 80, and redirects other HTTP requests to a fixed HTTPS hostname. Replace example.com with your domain.
package main

import (
	"io"
	"log"
	"net/http"
	"time"

	"golang.org/x/crypto/acme/autocert"
)

func main() {
	const domain = "example.com"
	manager := &autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist(domain),
		Cache:      autocert.DirCache("cert-cache"),
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		io.WriteString(w, "<h1>Welcome!</h1>")
	})

	httpsServer := &http.Server{
		Addr:              ":443",
		Handler:           mux,
		TLSConfig:         manager.TLSConfig(),
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       30 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       120 * time.Second,
	}
	redirect := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		target := *r.URL
		target.Scheme = "https"
		target.Host = domain
		target.User = nil
		http.Redirect(w, r, target.String(), http.StatusPermanentRedirect)
	})
	httpServer := &http.Server{
		Addr:              ":80",
		Handler:           manager.HTTPHandler(redirect),
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       30 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       120 * time.Second,
	}

	go func() {
		log.Fatal(httpServer.ListenAndServe())
	}()
	log.Fatal(httpsServer.ListenAndServeTLS("", ""))
}
manager.TLSConfig() configures certificate lookup and the ALPN protocols needed for TLS challenges. Passing empty certificate filenames tells ListenAndServeTLS to use that configuration. The method-qualified route requires Go 1.22 or later.
The HTTP challenge handler must run before the redirect. Using a fixed redirect hostname also avoids trusting an arbitrary incoming Host header. The redirect preserves the path and query, and status 308 preserves the request method.

Deployment details

The timeouts suit a small page-serving example; uploads and streaming handlers need their own timeout policy. A deployed service should also handle termination signals and call http.Server.Shutdown on both servers with a bounded context.
For local development, plain HTTP on loopback may be enough. Use locally trusted certificates when testing HTTPS-specific behavior such as secure cookies. A public CA cannot issue a certificate for localhost.

What a certificate proves

A domain-validated certificate proves control of a domain, not the legal identity or trustworthiness of its owner. Authentication is essential to HTTPS: encryption without authentication would still allow an active attacker to impersonate the server.
Let’s Encrypt made domain validation automatic and widely accessible. Its explanation of how issuance works describes the validation and renewal process.
The original example code accompanies the earlier version of this chapter. The example above uses the current autocert.Manager API.
#go
Sep 5 2026

Related articles

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you: