Running a HTTPS server
Now that we have a self-signed TLS certificate and corresponding private key, starting a HTTPS web server is lovely and simple — we just need open the main.go
file and swap the srv.ListenAndServe()
method for srv.ListenAndServeTLS()
instead.
Alter your main.go
file to match the following code:
package main ... func main() { addr := flag.String("addr", ":4000", "HTTP network address") dsn := flag.String("dsn", "web:pass@/snippetbox?parseTime=true", "MySQL data source name") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) db, err := openDB(*dsn) if err != nil { logger.Error(err.Error()) os.Exit(1) } defer db.Close() templateCache, err := newTemplateCache() if err != nil { logger.Error(err.Error()) os.Exit(1) } formDecoder := form.NewDecoder() sessionManager := scs.New() sessionManager.Store = mysqlstore.New(db) sessionManager.Lifetime = 12 * time.Hour // Make sure that the Secure attribute is set on our session cookies. // Setting this means that the cookie will only be sent by a user's web // browser when a HTTPS connection is being used (and won't be sent over an // unsecure HTTP connection). sessionManager.Cookie.Secure = true app := &application{ logger: logger, snippets: &models.SnippetModel{DB: db}, templateCache: templateCache, formDecoder: formDecoder, sessionManager: sessionManager, } srv := &http.Server{ Addr: *addr, Handler: app.routes(), ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), } logger.Info("starting server", "addr", srv.Addr) // Use the ListenAndServeTLS() method to start the HTTPS server. We // pass in the paths to the TLS certificate and corresponding private key as // the two parameters. err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem") logger.Error(err.Error()) os.Exit(1) } ...
When we run this, our server will still be listening on port 4000 — the only difference is that it will now be talking HTTPS instead of HTTP.
Go ahead and run it as normal:
$ cd $HOME/code/snippetbox $ go run ./cmd/web time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000
If you open up your web browser and visit https://localhost:4000/ you will probably get a browser warning because the TLS certificate is self-signed, similar to the screenshot below.

- If you’re using Firefox like me, click “Advanced” then “Accept the Risk and Continue”.
- If you’re using Chrome or Chromium, click “Advanced” and then the “Proceed to localhost (unsafe)” link.
After that the application homepage should appear (although it will still carry a warning in the URL bar).
In Firefox, it should look a bit like this:

If you’re using Firefox, I recommend pressing Ctrl+i
to inspect the Page Info for your homepage:

The ‘Security > Technical Details’ section here confirms that our connection is encrypted and working as expected.
In my case, I can see that TLS version 1.3 is being used, and the cipher suite for my HTTPS connection is TLS_AES_128_GCM_SHA256
. We’ll talk more about cipher suites in the next chapter.
Additional information
HTTP requests
It’s important to note that our HTTPS server only supports HTTPS. If you try making a regular HTTP request to it, the server will send the user a 400 Bad Request
status and the message "Client sent an HTTP request to an HTTPS server"
. Nothing will be logged.
$ curl -i http://localhost:4000/ HTTP/1.0 400 Bad Request Client sent an HTTP request to an HTTPS server.
HTTP/2 connections
A big plus of using HTTPS is that Go’s will automatically upgrade the connection to use HTTP/2 if the client supports it.
This is good because it means that, ultimately, our pages will load faster for users. If you’re not familiar with HTTP/2 you can get a run-down of the basics and a flavor of how it has been implemented behind the scenes in Go in this GoSF meetup talk by Brad Fitzpatrick.
If you’re using an up-to-date version of Firefox you should be able to see this in action. Press Ctrl+Shift+E
to open the Developer Tools, and if you look at the headers for the homepage you should see that the protocol being used is HTTP/2.

Certificate permissions
It’s important to note that the user that you’re using to run your Go application must have read permissions for both the cert.pem
and key.pem
files, otherwise ListenAndServeTLS()
will return a permission denied
error.
By default, the generate_cert.go
tool grants read permission to all users for the cert.pem
file but read permission only to the owner of the key.pem
file. In my case the permissions look like this:
$ cd $HOME/code/snippetbox/tls $ ls -l total 8 -rw-rw-r-- 1 alex alex 1090 Mar 18 16:24 cert.pem -rw------- 1 alex alex 1704 Mar 18 16:24 key.pem
Generally, it’s a good idea to keep the permissions of your private keys as tight as possible and allow them to be read only by the owner or a specific group.
Source control
If you’re using a version control system (like Git or Mercurial) you may want to add an ignore rule so the contents of the tls
directory are not accidentally committed. With Git, for instance:
$ cd $HOME/code/snippetbox $ echo 'tls/' >> .gitignore