Jan 13, 2018: This post has been updated to use HTTP challenge as Let’s Encrypt disabled the TLS-SNI challenge, which we were using before.
HTTPS has become a must nowadays. Not only for its security purpose, but also because search engines like Google are giving better rank to websites that run on a secure protocol over those using plain HTTP.
It’s 2017 and gone are the days that we could use the price as an excuse to not have HTTPS our our websites.
On this post I’ll explain how to create a Go web application that automatically generates SSL/TLS Certificates and use them to run itself on HTTPS. The best part is: It’s free!
If you wish to follow this demo and try it yourself, please make sure you comply with following requirements.
yourvm0001.yourcloud.net
.Let’s Encrypt is a very well known and trusted SSL/TLS certificate issuer that provides a free and automated generation process. It is possible to issue a certificate in less than a second without any registration process or payment.
Autocert is a Go package that implements a client of the ACME protocol used to generates certificates on Let’s Encrypt. This is the only package dependency that you will need, no other installation or package is required.
You can get it as any other Go package.
go get golang.org/x/crypto/acme/autocert
Those looking for more information on ACME or alternative packages, I recommend this talk dotGo 2016 - Matthew Holt - Go with ACME
package main
import (
"crypto/tls"
"fmt"
"net/http"
"golang.org/x/crypto/acme/autocert"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Secure World")
})
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache("certs"),
}
server := &http.Server{
Addr: ":443",
Handler: mux,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
go http.ListenAndServe(":80", certManager.HTTPHandler(nil))
server.ListenAndServeTLS("", "")
}
We start the main
function by creating a mux
with a simple Hello World message on path /
. In this example we’re using the Go’s default Mux, but it could be easily replaced by any other third-party router.
The next step we create an instance of autocert.Manager
. This struct is responsible for communicating with Let’s Encrypt and fetch the SSL/TLS certificates. The Cache
field is an interface that defines how and where autocert.Manager
should store and load the certificates from. In this example we’re using autocert.DirCache
that stores the certificates in a local folder. This is the easiest way to get started, but might not be the best one for websites hosted on multiple servers, because each server will have it’s own cache.
The last step is to create a http.Server
that listen on port 443
and uses our mux
instance. We then create tls.Config
object and assign it to the server. Now is where the “magic” happens. GetCertificate
is a method that we can use to tell the server where to load the certificate whenever a new HTTPS request is starting. This method gives us the opportunity to choose what certificate to use instead of returning a specific one for every request like most applications does. We then use certManager.GetCertificate
which will first try to get a matching certificate from the cache, if there’s none matching, then a new certificate is fetched from Let’s Encrypt using the ACME protocol.
Early 2018, Let’s Encrypt disabled TLS-SNI challenge due to security reasons. The recommendation is to use HTTP challenge, and we do so by using certManager.HTTPHandler(nil)
and attaching it a new http listener on port 80.
After that, all we need to do is start the server with server.ListenAndServeTLS("", "")
. If you’ve worked with a HTTPS server on Go before, you probably remember that these two parameters are the Certificate
and the Privatey Key
. When using autocert
, we don’t need these so we pass an empty string.
It worths to mention that, when using certManager.HTTPHandler(nil)
, all traffic comming into HTTP will be redirect to HTTPS automatically. You can override this behaviour by passing a http.Handler instead of nil as first parameter.
You can run it like any other Go web app, but it’ll fail if you do it on your local machine. The reason being that Let’s Encrypt requires the website to be publicly available through a know DNS name. When you run it locally, Let’s Encrypt cannot ping back your domain for verification purposes and it fails.
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o autossl
or different parameters if your target platform is not linux/amd64.Ta-Da! 🎉
You should now see the Hello Secure World
message and the green HTTPS lock icon.
You might notice that it might take a few seconds to load the first request. That’s because the SSL/TLS certificate generation process is happening on the background. Consecutive request should be lightning fast as the certificate is already cached.
If you have any question, feedback or suggestion, please feel free to drop me a comment. I’m happy to help if I can.
Thanks!