TLS Certificates
Vectis Mail encrypts all connections by default. HTTPS traffic is terminated by Traefik with automatic Let’s Encrypt certificates. Mail protocols (SMTP, IMAP, POP3) use a separate certificate managed by an acme.sh sidecar container. This dual-certificate architecture keeps the HTTP and mail TLS stacks independent and simplifies renewal.
Architecture overview
Section titled “Architecture overview”Vectis uses two separate TLS certificate paths:
| Protocol | Termination | Certificate Source | Renewal |
|---|---|---|---|
| HTTPS (443) | Traefik | Let’s Encrypt (HTTP-01) | Automatic |
| SMTP (465, 587) | Postfix | acme.sh sidecar | Automatic |
| IMAP (993) | Dovecot | acme.sh sidecar (shared) | Automatic |
| POP3 (995) | Dovecot | acme.sh sidecar (shared) | Automatic |
Both Postfix and Dovecot read from the same certificate files at /etc/ssl/mail/, which are provisioned by the acme.sh sidecar and shared via a Docker volume.
Why two certificate paths?
Section titled “Why two certificate paths?”Mail protocols (SMTP, IMAP, POP3) handle their own TLS directly — they are not routed through Traefik. This avoids the complexity of TCP passthrough configuration and STARTTLS handling in a reverse proxy. Each service manages its own TLS termination, giving you cleaner logs and simpler debugging.
Automatic provisioning with Let’s Encrypt
Section titled “Automatic provisioning with Let’s Encrypt”By default, Vectis uses Let’s Encrypt for all TLS certificates. This is configured in config.yaml:
tls: provider: letsencrypt email: admin@example.comHow it works
Section titled “How it works”For HTTPS (Traefik):
- Traefik uses the HTTP-01 challenge: Let’s Encrypt makes an HTTP request to port 80 on your server
- Traefik responds with a challenge token, proving you control the hostname
- Let’s Encrypt issues the certificate
- Traefik stores it in
/etc/traefik/acme/acme.jsonand automatically renews before expiry
For mail services (acme.sh sidecar):
- The acme.sh container runs a renewal script on startup and periodically thereafter
- It issues a certificate for your mail hostname using the standalone HTTP-01 challenge or DNS-01 (if configured)
- The certificate is installed to
/etc/ssl/mail/fullchain.pemand/etc/ssl/mail/privkey.pem - Postfix and Dovecot read these files from the shared
mail-certsDocker volume - On renewal, a reload signal is sent to Postfix and Dovecot
Requirements for HTTP-01 challenges
Section titled “Requirements for HTTP-01 challenges”HTTP-01 is the simplest method but has requirements:
- Port 80 must be open and reachable from the internet
- Your hostname’s A record must point to your server’s IP
- The hostname must be publicly resolvable
- You cannot be behind a proxy that intercepts port 80 traffic (unless it passes through ACME challenges)
If any of these are not met, use DNS-01 challenges instead.
DNS-01 challenges with Cloudflare
Section titled “DNS-01 challenges with Cloudflare”If you use Cloudflare for DNS, you can configure the acme.sh sidecar to use DNS-01 challenges. This is useful when:
- Port 80 is blocked or used by another service
- You want to issue wildcard certificates
- Your server is behind a firewall that blocks inbound HTTP
Configuration
Section titled “Configuration”Add your Cloudflare API token to secrets.yaml:
cloudflare: api_token: "your-cloudflare-api-token"The API token needs the following permissions:
- Zone > DNS > Edit for the zone(s) you want to issue certificates for
Then set dns_challenge: cloudflare in your TLS configuration. The acme.sh sidecar will use the Cloudflare API to create a temporary TXT record for domain validation, then clean it up after the certificate is issued.
How DNS-01 works
Section titled “How DNS-01 works”- acme.sh requests a certificate from Let’s Encrypt
- Let’s Encrypt provides a challenge token
- acme.sh creates a TXT record (
_acme-challenge.mail.example.com) via the Cloudflare API - Let’s Encrypt verifies the TXT record
- Certificate is issued
- acme.sh removes the temporary TXT record
This entire process is automatic and requires no manual intervention after initial configuration.
Custom certificates
Section titled “Custom certificates”If you have certificates from another CA (or an internal PKI), you can use them instead of Let’s Encrypt.
Configuration
Section titled “Configuration”Set the TLS provider to custom in config.yaml:
tls: provider: custom cert_path: /etc/vectis/certs/fullchain.pem key_path: /etc/vectis/certs/privkey.pemThe certificate and key files must be:
- PEM-encoded
- Readable by the Vectis containers (bind-mounted into the appropriate containers)
- The certificate file should include the full chain (your certificate + intermediate certificates)
Applying custom certificates
Section titled “Applying custom certificates”# Copy your certificate filescp your-cert.pem /etc/vectis/certs/fullchain.pemcp your-key.pem /etc/vectis/certs/privkey.pem
# Set permissionschmod 644 /etc/vectis/certs/fullchain.pemchmod 600 /etc/vectis/certs/privkey.pem
# Apply the configurationvectis config applyWhen using custom certificates, you are responsible for renewal. Vectis will not automatically renew them. Set a calendar reminder or use your own automation to replace the files before they expire, then run vectis config apply to reload.
TLS enforcement
Section titled “TLS enforcement”Vectis enforces TLS 1.2 as the minimum protocol version across all services. TLS 1.0 and 1.1 are disabled.
Postfix (SMTP)
Section titled “Postfix (SMTP)”# Inbound (other servers connecting to you)smtpd_tls_protocols = >=TLSv1.2smtpd_tls_mandatory_protocols = >=TLSv1.2smtpd_tls_security_level = maysmtpd_tls_auth_only = yes
# Outbound (your server connecting to others)smtp_tls_protocols = >=TLSv1.2smtp_tls_security_level = mayKey points:
- Inbound TLS is opportunistic (
may): Vectis will accept unencrypted connections from other servers on port 25 (required for interoperability — many legitimate mail servers still connect without TLS), but authentication is only allowed over TLS (smtpd_tls_auth_only = yes). - Submission ports (587, 465) require TLS: Port 587 uses STARTTLS and port 465 uses implicit TLS. Your email clients must connect with encryption.
- Outbound TLS is opportunistic (
may): Vectis will use TLS when the remote server supports it, but will fall back to plaintext if not. This is standard practice — enforcing TLS for outbound mail would prevent delivery to servers that don’t support it.
Dovecot (IMAP/POP3)
Section titled “Dovecot (IMAP/POP3)”ssl = requiredssl_min_protocol = TLSv1.2IMAP (993) and POP3 (995) use implicit TLS — the connection is encrypted from the first byte. Unencrypted IMAP (143) and POP3 (110) are not exposed.
Traefik (HTTPS)
Section titled “Traefik (HTTPS)”Traefik enforces TLS 1.2+ for all HTTPS connections. HTTP requests on port 80 are automatically redirected to HTTPS on port 443.
Certificate file locations
Section titled “Certificate file locations”| File | Path | Used by |
|---|---|---|
| Mail certificate chain | /etc/ssl/mail/fullchain.pem | Postfix, Dovecot |
| Mail private key | /etc/ssl/mail/privkey.pem | Postfix, Dovecot |
| Traefik ACME storage | /etc/traefik/acme/acme.json | Traefik |
| Custom certificates | /etc/vectis/certs/ | Configured in config.yaml |
Certificate renewal
Section titled “Certificate renewal”Let’s Encrypt (automatic)
Section titled “Let’s Encrypt (automatic)”Let’s Encrypt certificates are valid for 90 days. Both Traefik and acme.sh automatically renew certificates when they are within 30 days of expiry. No manual intervention is needed.
You can verify certificate expiry dates:
# Check the mail certificateopenssl s_client -connect mail.example.com:993 -servername mail.example.com </dev/null 2>/dev/null | \ openssl x509 -noout -dates
# Check the HTTPS certificateopenssl s_client -connect mail.example.com:443 -servername mail.example.com </dev/null 2>/dev/null | \ openssl x509 -noout -dates
# Check via the Vectis APIcurl https://mail.example.com/api/v1/health \ -H "Authorization: Bearer YOUR_TOKEN"Custom certificates (manual)
Section titled “Custom certificates (manual)”Replace the certificate files and reload:
cp new-fullchain.pem /etc/vectis/certs/fullchain.pemcp new-privkey.pem /etc/vectis/certs/privkey.pemvectis config applyVerifying TLS
Section titled “Verifying TLS”Test SMTP TLS
Section titled “Test SMTP TLS”# Test STARTTLS on port 587openssl s_client -starttls smtp -connect mail.example.com:587 -servername mail.example.com
# Test implicit TLS on port 465openssl s_client -connect mail.example.com:465 -servername mail.example.comTest IMAP TLS
Section titled “Test IMAP TLS”openssl s_client -connect mail.example.com:993 -servername mail.example.comTest POP3 TLS
Section titled “Test POP3 TLS”openssl s_client -connect mail.example.com:995 -servername mail.example.comTest HTTPS TLS
Section titled “Test HTTPS TLS”openssl s_client -connect mail.example.com:443 -servername mail.example.comIn all cases, look for:
Protocol : TLSv1.2orTLSv1.3in the outputVerify return code: 0 (ok)indicating a valid certificate chain- The correct subject (CN) and issuer
Troubleshooting
Section titled “Troubleshooting”Certificate not found
Section titled “Certificate not found”If Postfix or Dovecot fail to start with “certificate not found” errors, check that the acme.sh sidecar has completed its initial certificate issuance:
vectis logs acmedocker logs vectis-acmeThe sidecar needs port 80 access (or DNS-01 configuration) to issue the first certificate.
Certificate expired
Section titled “Certificate expired”If automatic renewal failed:
# Check acme.sh logs for errorsvectis logs acme
# Force a manual renewaldocker exec vectis-acme acme.sh --renew --domain mail.example.com --forceTLS handshake failures
Section titled “TLS handshake failures”If clients cannot connect:
# Test with verbose outputopenssl s_client -connect mail.example.com:993 -servername mail.example.com -debug
# Check the server's supported protocolsnmap --script ssl-enum-ciphers -p 993 mail.example.comCommon causes:
- Client requires TLS 1.0 or 1.1 (not supported — the client needs updating)
- Certificate hostname mismatch (the certificate’s CN or SAN does not match the hostname the client is connecting to)
- Incomplete certificate chain (the fullchain file is missing intermediate certificates)
Next steps
Section titled “Next steps”- Cloudflare integration for using Cloudflare DNS with Vectis
- Troubleshooting for common TLS and connectivity issues
- Installation guide for initial server setup