Home | Send Feedback

HTTP over TLS on localhost with Go server

Published: 28. November 2021  •  go

The web is moving to HTTPS. More and more sites are only accessible with HTTP over TLS. Thanks to Let's Encrypt, you have access to free TLS certificates and with the ACME protocol a way to automate the certificates management.

But there is one area where TLS is not that prevalent in our development environment. More and more features in the browser require a secure context. For example, Geolocation, Service Workers, Web Crypto, and others. These features only work when the page is served over HTTPS. Fortunately, browsers make an exception for connections to localhost and 127.0.0, and you can develop applications with these features in your development environment with HTTP over plaintext TCP.

But there is one feature that requires a TLS connection, HTTP/2 and HTTP/3. If you want to use HTTP/2 in your local development, you have to have TLS enabled. There is a specification for using HTTP/2 over cleartext TCP, but browsers have not implemented it. HTTP/3 also requires a TLS connection. Browser support is already quite good, but currently (November 2021), there is no HTTP/3 support in Go's standard library.

Another reason to use TLS in your development environment is the mixed content issue. For example, your application serves an HTML page that references the jQuery library with HTTP.

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Mixed Content</title>
</head>
<body>
    <script src="http://code.jquery.com/jquery-3.6.0.slim.min.js"></script> 
</body>
</html>

In your development environment, you use HTTP over cleartext TCP, and everything looks good and works. Then you deploy this web page to a production server that serves the resources over HTTPS. And suddenly, your application is no longer working because browsers refuse to load resources over an insecure connection when the host page has been loaded over a secure connection.

The browser prints this error message in the developer tools console:

Mixed Content: The page at 'https://localhost:8443/index.html' was loaded over HTTPS but requested an
insecure script 'http://code.jquery.com/jquery-3.6.0.slim.min.js'. This request has been blocked; 
the content must be served over HTTPS.

You see that there are reasons to develop and test your application with the same protocol you use in the production.

HTTP

Let's start with a simple HTTP server with one endpoint that returns "hello world". Thanks to the comprehensive standard library you can implement this in Go with just a few lines and without any 3rd party dependencies

package main

import (
  "fmt"
  "log"
  "net/http"
  "time"
)

func hello(w http.ResponseWriter, _ *http.Request) {
  fmt.Fprintf(w, "hello world")
}

func main() {
  mux := http.NewServeMux()
  mux.HandleFunc("/", hello)

  srv := &http.Server{
    Addr:         ":8080",
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 5 * time.Second,
    Handler:      mux,
  }

  log.Fatal(srv.ListenAndServe())
}

main.go

Run the program with go run main.go and open the URL http://localhost:8080 in a browser. You should see the text hello world.

Next, we take a look at how we can switch to HTTP/2 over TLS.

Self-signed

One possible solution to set up TLS on your local machine is by creating a self-signed certificate. Go brings as part of the development kit installation a tool with it that creates such a certificate. You find this program in the source code of the standard library. Look for the folder where you installed Go. In the folder src/crypto/tls/ you find the program generate_cert.go. Execute the program with the following command.

go run /usr/local/go/src/crypto/tls/generate_cert.go --rsa-bits=2048 --host=localhost

You should now find two new files in the current folder: cert.pem and key.pem

To switch to a TLS connection, replace the method ListenAndServe with the method ListenAndServeTLS and specify the certificate and key file.

  log.Fatal(srv.ListenAndServeTLS("./cert.pem", "./key.pem"))

main.go

Run the program with go run main.go and open the URL https://localhost:8443. You now see a downside of using self-signed certificates; browsers do not trust them and show a warning message that the user must accept to see the response.

browser-self-signed-tls-warning-message


mkcert

The program I'm going to show you next installs a private CA (certification authority), creates TLS certificates signed with this CA, and configures your operating system and browsers to trust this CA. This way, you will not see the ugly warning message in the browser.

Setting this up from scratch is possible with tools like openssl, but a bit complicated and error-prone. Fortunately, there is a free tool that simplifies the setup process: mkcert

It's a command-line tool written in Go and runs on Windows, Linux, and macOS. See the readme on how to install it.

After you have downloaded and installed the tool, run the -install command. This creates a private CA, configures the operating system and browsers to trust this CA.

mkcert.exe -install

If you want to know where mkcert installed the CA files, run mkcert -CAROOT. This command prints the configuration folder of mkcert. The install command registers the CA in the operating system certification store. Browsers like Chrome and Firefox trust root certificates in this store.

On Windows, you can verify the entry with the Windows certification manager tool.

certmgr.msc

You find the CA with the name "mkcert ..." under Trusted Root Certification Authorities -> Certificates


Next, create a TLS certificate for the domains localhost, 127.0.0.1 and ::1, that is signed by the mkcert CA.

mkcert.exe localhost 127.0.0.1 ::1

This command creates two files in the current directory: localhost+2.pem and localhost+2-key.pem

In our Go application, we now need to load these two files.

  log.Fatal(srv.ListenAndServeTLS("./localhost+2.pem", "./localhost+2-key.pem"))

main.go

Start the server and open the URL https://localhost:8443/ in a browser. The browser should now show you the response without any prior warning message. Check the network tab in the browser developer console. There you see that request and response was served with HTTP/2 over TLS.

network tab


You have seen in this article how easy it is to set up HTTP over TLS on localhost. mkcert is a convenient tool to create a valid TLS certificate that the browser accepts without showing a warning message.