Skip to content
ecdsa.com

Self-signed certificate · Go

How to create a self-signed EC P-256 certificate with SANs in Go

Go's standard library creates certificates without extra modules: fill an x509.Certificate template with the subject, the validity, the SANs and the key usages, and pass it to x509.CreateCertificate as both the certificate and its parent — that is what makes it self-signed. The run decodes cert.pem with OpenSSL and verifies it for localhost and 127.0.0.1.

Tested with Go 1.27.3 (crypto/x509, linux/arm64).

main.go — P-256 key and self-signed server certificate
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"net"
"os"
"time"
)
func main() {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
now := time.Now()
template := &x509.Certificate{
// No SerialNumber: since Go 1.24, CreateCertificate generates a random one
Subject: pkix.Name{CommonName: "localhost"},
NotBefore: now,
NotAfter: now.AddDate(0, 0, 365),
DNSNames: []string{"localhost"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true, // writes basicConstraints; IsCA false = CA:FALSE
}
// Self-signed: the template is both the certificate and its issuer
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
log.Fatal(err)
}
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("key.pem", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0o600); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("cert.pem", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o644); err != nil {
log.Fatal(err)
}
}

How it works

  1. ecdsa.GenerateKey(elliptic.P256(), rand.Reader) creates the key.
  2. DNSNames and IPAddresses become the SAN extension; NotBefore and NotAfter set the validity, here now.AddDate(0, 0, 365).
  3. BasicConstraintsValid: true with IsCA left false writes CA:FALSE; KeyUsage and ExtKeyUsage add digitalSignature and serverAuth, and Go marks basicConstraints and keyUsage critical by itself.
  4. x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) signs the template with its own key; x509.MarshalPKCS8PrivateKey and pem.EncodeToMemory produce the two files.

Gotchas

  • SerialNumber can stay nil since Go 1.24, which then generates a random RFC 5280 serial. Earlier versions fail with x509: no SerialNumber given — there, set it from rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)).
  • Without BasicConstraintsValid: true, Go writes no basicConstraints extension at all, even though IsCA is false.
  • Addresses belong in IPAddresses, not in DNSNames: a certificate without IP SANs fails VerifyHostname("127.0.0.1") with x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs.
  • KeyUsageKeyEncipherment is for RSA keys only — Go's own crypto/tls/generate_cert.go sets it just for RSA; an ECDSA server certificate needs digitalSignature.

Related recipes