Self-Signed TLS Certificate Generation and HTTPS Server
Generates a self-signed certificate on startup, writes PEM files, and launches an HTTPS server for local development.
main.go
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"log"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
"time"
)
func generateSelfSigned(hosts []string) (certPEM, keyPEM []byte, err error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
serial, err := rand.Int(rand.Reader, big.NewInt(1<<62))
if err != nil {
return nil, nil, err
}
tpl := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
Organization: []string{"Local Dev Cert"},
CommonName: "localhost",
},
NotBefore: time.Now().Add(-5 * time.Minute),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
tpl.IPAddresses = append(tpl.IPAddresses, ip)
} else {
tpl.DNSNames = append(tpl.DNSNames, h)
}
}
der, err := x509.CreateCertificate(rand.Reader, &tpl, &tpl, &priv.PublicKey, priv)
if err != nil {
return nil, nil, err
}
certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
keyPEM = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
return certPEM, keyPEM, nil
}
func main() {
cert, key, err := generateSelfSigned([]string{"localhost", "127.0.0.1"})
if err != nil {
log.Fatalf("generate: %v", err)
}
dir := os.TempDir()
certPath := filepath.Join(dir, "dev-cert.pem")
keyPath := filepath.Join(dir, "dev-key.pem")
if err := os.WriteFile(certPath, cert, 0o600); err != nil {
log.Fatalf("write cert: %v", err)
}
if err := os.WriteFile(keyPath, key, 0o600); err != nil {
log.Fatalf("write key: %v", err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello over https")
})
srv := &http.Server{Addr: ":8443", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
log.Printf("cert=%s key=%s", certPath, keyPath)
log.Printf("https server on https://localhost:8443")
if err := srv.ListenAndServeTLS(certPath, keyPath); err != nil {
log.Fatalf("serve: %v", err)
}
}How It Works
Generates a self-signed certificate on startup, writes PEM files, and launches an HTTPS server for local development.
Creates an RSA key, builds an x509 certificate template, signs it, writes certificate and key to disk, configures tls.Config for an HTTP server, and serves a simple handler over HTTPS.
Key Concepts
- 1Dynamic certificate generation removes dependency on pre-made certs.
- 2Serves HTTPS with http.Server and tls.Config using generated keys.
- 3PEM output can be reused by other development tools.
When to Use This Pattern
- Local HTTPS testing without external certificate authorities.
- Spinning up TLS endpoints in CI for integration tests.
- Prototyping mTLS or secure webhooks locally.
Best Practices
- Protect generated keys with filesystem permissions.
- Use proper CA-signed certificates in production, not self-signed.
- Configure TLS versions and ciphers according to security guidelines.
Go Version1.18+
Difficultyadvanced
Production ReadyYes
Lines of Code89