Fix 'x509: certificate signed by unknown authority' Error in Go HTTPS Requests

intermediate๐Ÿ”ท Go2026-05-15| Go 1.16+, Linux / macOS / Windows, any HTTPS endpoint with self-signed or private CA certificates

Error Message

Get "https://example.com": x509: certificate signed by unknown authority
#tls#https#x509#certificate#net/http

The Error

You make an HTTPS request with Go's net/http package and get:

Get "https://example.com": x509: certificate signed by unknown authority

The server's TLS certificate is real and valid โ€” Go just doesn't trust the CA that issued it. Your request dies before any data is exchanged.

Why This Happens

Go validates TLS certificates against the system's CA (Certificate Authority) store. That validation fails when:

  • The server uses a self-signed certificate โ€” common in dev and staging environments
  • The cert was signed by a private/internal CA not registered in your OS trust store
  • A corporate proxy or VPN is doing TLS inspection and re-signing traffic with its own CA (Zscaler, Palo Alto NGFW, Cisco Umbrella)
  • You're in a minimal Docker container (scratch or alpine) with no CA certs installed at all
  • The system CA bundle is outdated or simply missing

Fix 1: Add the CA Certificate to the System Trust Store

For production and shared hosts, add the CA cert to the OS trust store once. Every Go binary on that machine picks it up automatically โ€” no code changes needed.

Linux (Debian/Ubuntu)

# Copy your CA cert (PEM format) into the trust store
sudo cp your-ca.crt /usr/local/share/ca-certificates/your-ca.crt
sudo update-ca-certificates

Linux (RHEL/CentOS/Fedora)

sudo cp your-ca.crt /etc/pki/ca-trust/source/anchors/your-ca.crt
sudo update-ca-trust

macOS

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain your-ca.crt

After adding the cert, restart your Go application. It reads the system store at startup, not at every request.

Fix 2: Load the CA Certificate Programmatically

Can't touch the system trust store? Load the CA cert directly in your Go code instead. This works well on shared servers, read-only containers, or anywhere the OS is off-limits.

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    // Read your CA certificate file (PEM format)
    caCert, err := os.ReadFile("your-ca.crt")
    if err != nil {
        panic(err)
    }

    // Add it to the system pool
    caCertPool, err := x509.SystemCertPool()
    if err != nil {
        // Fallback: create an empty pool
        caCertPool = x509.NewCertPool()
    }
    caCertPool.AppendCertsFromPEM(caCert)

    // Build an HTTP client that uses the extended pool
    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                RootCAs: caCertPool,
            },
        },
    }

    resp, err := client.Get("https://example.com")
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Your CA cert travels with the binary. No OS changes, no system-wide side effects.

Fix 3: Fix Missing CA Certs in Docker Containers

Minimal base images โ€” scratch, alpine, distroless โ€” ship with zero CA certificates. The binary compiles fine. At runtime, the first HTTPS call crashes.

Option A โ€” Install ca-certificates in Alpine

FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY your-app /app
ENTRYPOINT ["/app"]

Option B โ€” Copy CA certs from a builder stage (scratch images)

FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o app .

FROM scratch
# Copy system CA bundle from the builder
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /src/app /app
ENTRYPOINT ["/app"]

Fix 4: Skip TLS Verification (Development Only)

This option exists. Use it only for local testing. In production, InsecureSkipVerify: true means any server โ€” including a man-in-the-middle โ€” can present any certificate and your code accepts it without question.

client := &http.Client{
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            InsecureSkipVerify: true, // โš ๏ธ DEV ONLY
        },
    },
}

Found this in production code? Fix the root cause with Fix 1 or Fix 2.

Fix 5: Corporate Proxy / VPN TLS Inspection

A tell-tale sign: your colleagues on the same network aren't hitting this error, but you are. A proxy is likely intercepting TLS traffic and re-signing it with its own CA. Ask your IT or security team for the proxy's root CA certificate, then apply Fix 1 or Fix 2 with that cert.

To see exactly which certificate the server is presenting, run:

openssl s_client -connect example.com:443 < /dev/null 2>&1 | openssl x509 -noout -issuer -subject

If the issuer shows "Zscaler Root CA" or your company name instead of DigiCert or Let's Encrypt, that's the proxy cert you need to trust.

Verify the Fix

Run your Go program again. No TLS error means the fix worked. For a manual check, use OpenSSL directly:

# Run your program with verbose output
go run -v your_main.go

# Verify the cert chain against your CA file
openssl s_client -connect example.com:443 -CAfile your-ca.crt

When OpenSSL reports Verify return code: 0 (ok), the chain is clean. Go will accept it.

Prevention

  • Store internal CAs in version control: Put company CA certs in a shared repo or config management tool (Ansible, Chef, Puppet). Every new host gets them on provisioning โ€” no manual steps.
  • Always bundle ca-certificates in Docker images that make outbound HTTPS calls. Don't wait for a production incident to discover it was missing.
  • Ban InsecureSkipVerify in CI: A one-line grep catches it before it merges โ€” grep -r InsecureSkipVerify ./ in your PR pipeline.
  • Load CA paths from environment variables: The same binary then works in dev (self-signed cert) and prod (public CA) without any code changes.

Related Error Notes