Native TLS 1.3 serving path with three cipher suites, automatic Let's Encrypt certificates, SNI-based certificate selection, hot-reload, and TLS 1.2 compatibility fallback.
ALOS ships a native TLS 1.3 serving path built from scratch. The handshake,
record layer, and cryptographic operations used on that path are custom-written in Go with
no dependency on crypto/tls for TLS 1.3 serving. Older TLS 1.2/1.1 clients
can still fall back through Go's compatibility path when needed.
h2 and http/1.1Three TLS 1.3 cipher suites are supported, negotiated automatically based on client preference:
Suite ID: 0x1301
AES-128-GCM encryption
SHA-256 hashing
16-byte key,
12-byte IV
Suite ID: 0x1302
AES-256-GCM encryption
SHA-384 hashing
32-byte key,
12-byte IV
Suite ID: 0x1303
ChaCha20-Poly1305
SHA-256 hashing
32-byte key, 12-byte
IV
Load certificates from PEM files:
s := core.New(core.Config{ Addr: ":443", TLSCertFile: "/etc/ssl/cert.pem", TLSKeyFile: "/etc/ssl/key.pem", }) s.AddCertFromFiles("example.com", "cert.pem", "key.pem") s.AddCert("example.com", certPEM, keyPEM)
ALOS auto-generates self-signed certs for development when no certificate is configured:
s := core.New(core.Config{Addr: ":443"}) s.ListenAndServeTLS() s.AddSelfSignedCert("dev.local")
Self-signed certs are saved to .alos-cert.pem and .alos-key.pem and
reused on subsequent runs. They use P-256 ECDSA keys with 365-day validity.
Serve different certificates for different domains using Server Name Indication (SNI):
s := core.New(core.Config{ Addr: ":443", Certs: []core.CertConfig{ {Domain: "example.com", CertFile: "example.pem", KeyFile: "example-key.pem"}, {Domain: "api.example.com", CertFile: "api.pem", KeyFile: "api-key.pem"}, }, DefaultDomain: "example.com", }) s.AddCertFromFiles("new.example.com", "new-cert.pem", "new-key.pem") s.SetDefaultCert("example.com") s.RemoveCert("old.example.com")
certs := s.ListCerts() for _, c := range certs { fmt.Printf("Domain: %s, Source: %d\n", c.Domain, c.Source) }
Automatic certificate issuance and renewal via the ACME protocol (Let's Encrypt):
s := core.New(core.Config{ Addr: ":443", HTTPAddr: ":80", ACME: &core.ACMEConfig{ Email: "admin@example.com", Domains: []string{"example.com", "www.example.com"}, CacheDir: ".alos-certs", }, })
In a multi-node deployment, set ACMENode to route all HTTP-01 challenge traffic
to a dedicated node. Port 80 will proxy /.well-known/acme-challenge/ requests
to the specified address instead of handling them locally:
s := core.New(core.Config{ Addr: ":443", HTTPAddr: ":80", ACME: &core.ACMEConfig{ Email: "admin@example.com", Domains: []string{"example.com"}, CacheDir: ".alos-certs", ACMENode: "10.0.0.5:80", }, })
When ACMENode is empty (the default), challenges are handled on the local
machine. When set, every ACME challenge request arriving on port 80 is proxied to the
configured address.
| Field | Type | Description |
|---|---|---|
Email |
string |
Contact email for Let's Encrypt account |
CacheDir |
string |
Directory for cached certs (default: /etc/letsencrypt) |
Domains |
[]string |
Domains to obtain certs for |
ACMENode |
string |
Dedicated node address for ACME challenge proxying (e.g.
10.0.0.5:80) |
CacheDirRequirements for ACME:
Full runtime certificate management API:
| Method | Description |
|---|---|
AddCert(domain, certPEM, keyPEM) |
Add certificate from PEM bytes |
AddCerts(domain, certPEM, keyPEM) |
Alias for AddCert |
AddCertFromFiles(domain, certFile, keyFile) |
Add from PEM files |
AddSelfSignedCert(domain) |
Generate and add self-signed cert |
UpdateCert(domain, certPEM, keyPEM) |
Hot-replace a certificate from PEM bytes |
ReloadCert(domain, certFile, keyFile) |
Hot-replace a certificate from PEM files |
RemoveCert(domain) |
Remove a certificate |
SetDefaultCert(domain) |
Set fallback cert for unknown SNI |
ListCerts() |
List all loaded certificates |
| Source | Description |
|---|---|
CertSelfSigned |
Auto-generated on startup |
CertManual |
Loaded from PEM files |
CertACME |
Obtained via Let's Encrypt |
Replace certificates at runtime without restarting the server. Uses lock-free atomic snapshots for zero-downtime updates:
err := s.ReloadCert("example.com", "new-cert.pem", "new-key.pem")
Or update directly from PEM bytes you loaded yourself:
certPEM, _ := os.ReadFile("renewed-cert.pem") keyPEM, _ := os.ReadFile("renewed-key.pem") err := s.UpdateCert("example.com", certPEM, keyPEM)
Both methods atomically swap the certificate in the CertStore and rebuild the
fallback TLS config. Existing connections continue with their original cert; new connections
pick up the replacement immediately.
Automatically redirect all HTTP traffic to HTTPS. The HTTP listener also serves ACME challenges:
s := core.New(core.Config{ Addr: ":443", HTTPAddr: ":80", })
When HTTPAddr is set, port 80 serves ACME HTTP-01 challenges at
/.well-known/acme-challenge/ and redirects all other traffic to HTTPS with a
301 status.
Add domains dynamically after the server starts. Certificates are obtained automatically:
s.AddACMEDomain("shop.example.com") s.AddACMEDomain("blog.example.com", "admin@blog.example.com")
Or enable ACME after server creation:
s := core.New(core.Config{ Addr: ":443", HTTPAddr: ":80", }) s.EnableACME(core.ACMEConfig{ Email: "admin@example.com", Domains: []string{"example.com"}, CacheDir: ".alos-certs", })
| Method | Description |
|---|---|
EnableACME(cfg ACMEConfig) |
Enable ACME with full configuration |
AddACMEDomain(domain string, email ...string) |
Add a single domain for ACME cert issuance |
AddDomainCert(domain string, certPEM, keyPEM []byte) error |
Manually add a cert for a domain (used internally by ACME) |
A complete production TLS server with ACME, manual fallback certs, and HTTP redirect:
s := core.New(core.Config{ Addr: ":443", HTTPAddr: ":80", ACME: &core.ACMEConfig{ Email: "admin@example.com", Domains: []string{"example.com", "www.example.com"}, CacheDir: "/etc/alos/certs", }, HandshakeTimeout: 10 * time.Second, }) s.Router.Use(core.Recovery(), core.Logger()) s.Router.GET("/", func(req *core.Request, resp *core.Response) { resp.String("Secure!") }) s.Router.Build() log.Fatal(s.ListenAndServeTLS())
Route all ACME challenges to a single dedicated node in a cluster:
s := core.New(core.Config{ Addr: ":443", HTTPAddr: ":80", ACME: &core.ACMEConfig{ Email: "admin@example.com", Domains: []string{"example.com"}, CacheDir: "/etc/alos/certs", ACMENode: "10.0.0.5:80", }, })
Mix ACME with manually loaded certs for internal domains:
s.AddCertFromFiles("internal.corp", "internal.pem", "internal-key.pem") s.AddSelfSignedCert("dev.internal.corp") certPEM, _ := os.ReadFile("renewed-cert.pem") keyPEM, _ := os.ReadFile("renewed-key.pem") s.UpdateCert("example.com", certPEM, keyPEM) certs := s.ListCerts() for _, c := range certs { fmt.Printf("Domain: %s, Source: %d\n", c.Domain, c.Source) }