How to create a locally trusted HTTPS server in Linux

If you're developing an application that requires HTTPS, you'll need a server with SSL/TLS certificates. While self-signed certificates can be used, browsers and systems don't trust them by default, leading to security warnings. In this guide, I'll show you how to create your own Certificate Authority (CA) and generate locally trusted certificates without using tools like mkcert. I'll also set up an HTTPS server in Go, but the principles apply to any programming language.

Jorge Martinez | Sunday, December 1, 2024


Step 1: Create a Certificate Authority (CA)

We'll start by generating our own CA, which will sign the server certificate.

What is a Certificate Authority (CA)?

A Certificate Authority (CA) is an entity that issues digital certificates. These certificates confirm the identity of the server or client and establish trust in the encrypted connection. By creating your own CA, you take on the role of a trusted entity for your local environment.

Create directories

mkdir -p https_server/certs && cd https_server/certs

Generate CA private key

openssl genrsa -des3 -out myCA.key 2048
  • Note: You'll be prompted to set a passphrase. Remember it; you'll need it later.

Generate CA certificate

openssl req -x509 -new -nodes -key myCA.key -sha256 -days 1825 -out myCA.pem
  • When prompted, you can leave fields blank or fill them as needed.

Step 2: Install the CA certificate in the System Trust Store

Adding your CA to the system's trusted root certificates ensures that certificates signed by your CA are trusted.

Install ca-certificates package

sudo apt update
sudo apt install -y ca-certificates

Add your CA certificate

sudo cp myCA.pem /usr/local/share/ca-certificates/myCA.crt
sudo update-ca-certificates
  • Output should include:
    1 added, 0 removed; done.
    Adding debian:myCA.pem
    

Step 3: Generate a server certificate signed by your CA

Generate server private key

openssl genrsa -out server.key 2048

Create a Certificate Signing Request (CSR)

CSR contains information (e.g. common name, organization, country) the Certificate Authority (CA) will use to create your certificate. It also contains the public key that will be included in your certificate and is signed with the corresponding private key.

openssl req -new -key server.key -out server.csr

Create a configuration file for extensions

Create a file named server.ext with the following content:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost

Generate the server certificate

openssl x509 -req -in server.csr -CA myCA.pem -CAkey myCA.key -CAcreateserial -out server.crt -days 825 -sha256 -extfile server.ext
  • Note: Enter the CA passphrase when prompted.

Step 4: Install the CA certificate in your browser

As the final step, add your CA certificate to your browser's trusted authorities.

Google Chrome

  1. Navigate to Settings > Privacy and security > Security.
  2. Scroll down and click Manage certificates.
  3. Select the Authorities tab.
  4. Click Import and choose your myCA.pem file.
  5. Check all trust options and confirm.

Step 5: Create an HTTPS server in Go

Now, let's set up a simple HTTPS server using Go.

Write the server code

Create a file named main.go in ~/https_server:

package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"
)

func index(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.Write([]byte("Hello world!!!.\n"))
}

func ExecServer() error {
	mux := http.NewServeMux()
	mux.HandleFunc("/", index)

	// Load tls certificates
	serverTLSCert, err := tls.LoadX509KeyPair("certs/server.crt", "certs/server.key")
	if err != nil {
		log.Fatalf("Error loading certificate and key file: %v", err)
	}
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{serverTLSCert},
	}

	serv := &http.Server{
		Addr:           ":8080",
		Handler:        mux,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
		TLSConfig:      tlsConfig,
	}

	fmt.Println("### Server is starting... Listening on https://localhost:8080 ###")

	defer serv.Close()
	log.Fatal(serv.ListenAndServeTLS("", ""))

	return nil
}

func main() {
	if err := ExecServer(); err != nil {
		fmt.Fprintf(os.Stderr, "%s\n", err)
		os.Exit(1)
	}
}

Run the server

cd ~/https_server
go run main.go

Open https://localhost:8080 in your browser.

You should see something like this:

Server response

Congratulations 🎉 🎉, you have an https server running using locally trusted certificates.

Remember that for production you have to obtain a certificate from a trusted CA.


ShareSubscribe
As always thank you for taking the time to read this blog post!