Secure a Golang Web Server with a Self-signed or Let's Encrypt SSL Certificate

Updated on November 16, 2021
Secure a Golang Web Server with a Self-signed or Let's Encrypt SSL Certificate header image

Introduction

Golang's inbuilt net/http package allows you to create and serve web applications without installing third-party web servers. However, to protect client-server communications in your applications, you must use an SSL/TLS certificate. Luckily, the Golang rich HTTPS libraries provide this functionality.

An SSL/TLS certificate encrypts data so that it can only be read by the intended recipients. It is very useful in scenarios where you're collecting sensitive information such as credit card numbers and authentication details. SSL certificates improve your customers' trust and affirm your business identity.

In this guide, you'll learn how to install, configure, and use either a self-signed or a Let's Encrypt certificate on your Golang applications hosted on a Linux server.

Prerequisites

To complete testing this Golang SSL/TLS certificate guide, make sure you have the following:

1. Create a Simple HTTP Server

SSH to your Linux server and complete the following steps to create an HTTP server.

  1. Make a new project directory under your home directory. This separates your project from the rest of the Linux files to make troubleshooting easier if you encounter errors.

     $ mkdir project
  2. Navigate to the new project directory.

     $ cd project
  3. Use the nano text editor to create a new http.go file.

     $ nano http.go
  4. Paste the following information into the file.

     package main
    
     import (
         "net/http"
         "fmt"  
     )
    
     func main() {
         http.HandleFunc("/", httpRequestHandler)
         http.ListenAndServe(":8081", nil)
     }
    
     func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
     fmt.Fprintf(w, "Hello, World!")         
     }
  5. Save and close the file when you're through with editing.

  6. In the above file, you've imported the net/http package. This allows you to implement the HTTP client and server libraries in your Golang application.

  7. You've also imported the fmt package to print a Hello, World! message when a client queries your HTTP server.

  8. Under the main function (func main(){...}), you're calling the http.HandleFunc(...) method. This method accepts two arguments. That is, the URL pattern to match and the handler function that provides the HTTP response. For this guide, you're defining / as the URL pattern to match all HTTP requests. Next, you've defined httpRequestHandler(...) as the handler function, and under it, you're printing a Hello, World! message to HTTP clients.

  9. To initiate the web server, you're calling the function http.ListenAndServe(...). This method accepts the port number (for instance 8081) and a handler function. In this guide, you've used nil as the handler function to instruct the http package to use the DefaultServeMux.Handle handler that you've already populated using the http.HandleFunc(...)

  10. Run the http.go application. Please note, this command has a blocking function meaning that your application is now listening on port 8081.

     $ go run http.go
  11. Next, visit the following address on a web browser. Replace 192.0.2.1 with the correct public IP address of your server. Remember to append port :8081 at the end of the address.

    You should now get the Hello, World! response, indicating that your web server is working as expected.

     Hello, World!
  12. While you can now begin building your HTTP application by extending the code in the http.go file, your app is not secure and sensitive data can be sniffed and captured by hackers when traversing through the network. To safeguard your client's data and make your application secure, this is where an SSL/TLS certificate comes into play.

  13. In the next step, you'll secure your application with a self-signed SSL certificate. Terminate the session by pressing Ctrl + C

2. Secure the Server with a Self-Signed Certificate

Self-signed SSL certificates do not require third-party Certificate Authorities(CAs). They're easy to integrate into your existing Golang HTTP applications and come at no cost. Follow the steps below to generate the certificate.

  1. Use the openssl command below to create a private key (go-server.key) and a self-signed certificate (go-server.crt) valid for 365 days with a key size of 2,048 bits.

     $ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout go-server.key -out go-server.crt
  2. Depending on your organization name and current location, respond to each question appropriately. Make sure you've provided the correct public IP address or domain name when prompted to enter the Common Name.

     Country Name (2 letter code) [AU]: US
     State or Province Name (full name) [Some-State]: CALIFORNIA.
     Locality Name (eg, city) []: LOS ANGELES.
     Organization Name (eg, company) [Internet Widgits Pty Ltd]: SAMPLE COMPANY
     Organizational Unit Name (eg, section) []: SECURITY DEPARTMENT
     Common Name (e.g. server FQDN or YOUR name) []: SAMPLE_DOMAIN or PUBLIC_IP_ADDRESS
     Email Address []: Enter your email address e.g. info@example.com
  3. Confirm that you've generated the private key and self-signed certificate by listing the files under your current project directory.

     $ ls

    You should now see the go-server.crt and go-server.key files on the file list.

     go-server.crt  go-server.key ...
  4. Next, remove the old http.go file and create a new https.go file.

     $ rm http.go
     $ nano https.go
  5. Paste the information below into the https.go file.

     package main
    
     import (
         "net/http"
         "fmt"  
     )
    
     func main() {
         http.HandleFunc("/", httpRequestHandler)
         http.ListenAndServeTLS(":8081","go-server.crt","go-server.key", nil)
     }
    
     func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
     fmt.Fprintf(w, "Hello, World!")         
     }
  6. Save and close the file when you're through with editing. The new https.go file is almost similar to the http.go file you created earlier. However, the new file uses the secure SSL/TLS function http.ListenAndServeTLS(...) instead of the unencrypted http.ListenAndServe(...) function.

    The new ListenAndServeTLS(...) accepts two more parameters. That is the certFile and the keyFile. This is where you define the file paths for the private key and the self-signed certificate(http.ListenAndServeTLS(":8081","go-server.crt","go-server.key", nil)).

  7. Run the new https.go file.

         $ go run https.go
  8. Again, open a browser and enter your Golang's server domain name or public IP address. This time around, begin the address with the https protocol instead of http.

  9. Since you're using a self-signed certificate that a trusted CA does not issue, your browser should complain and display some warnings. Next, bypass the SSL/TLS security warnings to visit your application. You should now get the Hello, World! message.

     Hello, World!
  10. Any further requests made to your Golang application are now encrypted. The approach of using a self-signed certificate is only suitable for testing purposes or for internal applications that you don't intend to release to the public. In the next step, you'll create a fully-developed SSL/TLS certificate from a trusted CA.

3. Secure the Server with a Let's Encrypt Certificate

Let's Encrypt is a non-profit trusted CA that provides free SSL certificates that are just as good as paid certificates. The organization provides different tools, libraries, and APIs to generate and create SSL certificates automatically. Follow the steps below to create and use their certificate.

  1. Delete the old https.go file and re-create it.

     $ rm https.go
     $ nano https.go
  2. Paste the information below into the file. Replace example.com with the correct domain name. Make sure you've configured DNS for your domain.

     package main
    
     import (
         "net/http"
         "fmt"
         "crypto/tls"
         "golang.org/x/crypto/acme/autocert"
         "log"
     )
    
     func main() {
    
         certManager := autocert.Manager{
             Prompt:     autocert.AcceptTOS,
             HostPolicy: autocert.HostWhitelist("example.com", "www.example.com"),
             Cache:      autocert.DirCache("certs"),
         }        
    
         server := &http.Server{
             Addr: ":https",
             TLSConfig: &tls.Config{
                 GetCertificate: certManager.GetCertificate,
             },
         }
    
         http.HandleFunc("/", httpRequestHandler)
    
         go http.ListenAndServe(":http", certManager.HTTPHandler(nil))
    
         log.Fatal(server.ListenAndServeTLS("", ""))
    
     }
    
     func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
     fmt.Fprintf(w, "Hello, World!")         
     }
  3. Save and close the file when you're through with editing. In the above file, you're using the autocert manager package(golang.org/x/crypto/acme/autocert) to pull the Let's Encrypt certificate into your server under the certs directory. In case the certs directory doesn't exist, the autocert manager creates it with 0700 permissions.

    Remember to add the domain names and any aliases in the autocert.HostWhitelist(...) to allow the autocert manager to retrieve certificates for them and respond appropriately.

  4. Add the golang.org/x/crypto/acme/autocert library to your project.

     $ go get "golang.org/x/crypto/acme/autocert"
  5. This time around, don't run the https.go file using the command go run https.go because you're going to encounter some errors since Linux won't allow the application to bind to any privileged TCP/IP ports below 1024. Instead, use the Golang build command to create an executable binary, copy it to the /usr/local/bin/ directory, and run the setcap command against the new binary to grant it the appropriate permissions.

     $ go build https.go
     $ sudo cp https /usr/local/bin/https
     $ sudo setcap CAP_NET_BIND_SERVICE+ep /usr/local/bin/https
  6. Run the https binary.

     $ https
  7. Visit the address below on your browser. You don't have to enter port 443 at the end.

    You should now get the Hello, World! message without any SSL/TLS warnings.

     Hello, World!

    This confirms that your Let's Encrypt certificate is now working as expected.

Conclusion

In this guide, you've configured a simple Golang HTTP server and learned how you could either secure it with a self-signed or a Let's Encrypt certificate. Follow the steps in this guide to secure your web applications with SSL/TLS certificate when coding your next Golang project.