Pocket Gophers

Serving HTTPS

If you’ve been building a web server in Go, at some point you will need to figure out how to serve your site with HTTPS.

One option is to use a load-balancing reverse-proxy such as Træfik or Caddy, letting it handle HTTPS while your application server(s) remain on HTTP.

If, however, your Go code needs to serve HTTPS directly, for example to take advantage of HTTP/2 Server Push, the main difficulty you will encounter is managing TLS certificates. There are two main ways to handle this: bring your own certificate, and automatic certificate management using a certification authority (CA) that supports the Automatic Certificate Management Environment (ACME) such as Let’s Encrypt.

Both options are straightforward to accomplish with Go. You can get the example code for this post with:

go get -d pocketgophers.com/serving-https

Run each example with:

go run example.go

Replacing example.go with the example you want to run.

Hello, 世界

Since you are starting from a working HTTP server, so will I:

http.go
package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", hello)

	log.Fatal(http.ListenAndServe(":http", nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, 世界"))
}

This is a basic web server that runs on port http (i.e., 80) and responds to every request with Hello, 世界. Your code may vary, especially if you are not directly using net/http. Take the time now to recreate this example using the same environment your web server uses so you will know how the examples in this post relate to your own code.

Most of the code in this example will not change. All changes will affect the line:

log.Fatal(http.ListenAndServe(":http", nil))

The way errors are handled (with log.Fatal) and the http.Handler used ( nil, meaning to use http.DefaultServeMux) are not important for the purposes of this post.

Now that we have a common starting point, let’s make this web server serve HTTPS.

If you have a certificate

… use http.ListenAndServeTLS:

https.go
package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", hello)

	log.Fatal(http.ListenAndServeTLS(
		":https", "cert.pem", "key.pem", nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, 世界"))
}

Your certificate and matching private keys are stored in cert.pem and key.pem. These are paths to files. If your certificate and key are not stored on disk, look at the docs and code for *Server.ListenAndServeTLS(certFile, keyFile string) to start figuring out how to handle your situation.

It is your responsibility to handle all aspects of the certificate, especially updating it before it expires. The method shown in this example requires your web server to be restarted to load the new certificate. Use tls.Config.GetCertificate to implement a more dynamic method.

If you don’t have a certificate

… you can get one and have it automatically updated before it expires by using golang.org/x/crypto/acme/autocert. This solution relies on three components:

autocert.go
package main

import (
	"log"
	"net/http"

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

func main() {
	http.HandleFunc("/", hello)

	m := &autocert.Manager{
		Prompt:     autocert.AcceptTOS,
		HostPolicy: autocert.HostWhitelist("example.com"),
		Cache:      autocert.DirCache("golang-autocert"),
	}

	// HTTP Server
	go func() {
		log.Fatal(http.ListenAndServe(":http",
			m.HTTPHandler(nil)))
	}()

	// HTTPS Server
	log.Fatal(http.Serve(m.Listener(), nil))
}

func hello(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello, 世界"))
}

This will starts web servers on ports 80 (HTTP) and 443 (HTTPS), and automatically acquires and renew certificates with the following defaults:

NOTE: your server must be accessible on the public internet on ports 80 and 443, and have its domain name findable by the CA’s server. These are needed to prove to the CA that the server requesting the certificate serves the domain name and it is therefore reasonable to issue the certificate.

Dig into the Fundamentals of Go

Subscribe to receive a weekly email covering a Go fundamental. Be it the language, its tooling, or its packages, you will learn what you need to know.