Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.allthingslinux.org/llms.txt

Use this file to discover all available pages before exploring further.

atl.sh uses TLS for HTTPS (nginx), Gemini (molly-brown), and FTP (vsftpd). Production and staging use Let’s Encrypt certificates. The dev VM uses self-signed certs.

Certificate overview

ServicePortCert sourcePath
nginx (HTTPS)443Let’s Encrypt/etc/letsencrypt/live/atl.sh/
molly-brown (Gemini)1965Let’s Encrypt (prod) / self-signed (dev)See below
vsftpd (FTPS)21Shared with nginx (prod) / self-signed (dev)See below

Production / staging paths

/etc/letsencrypt/live/atl.sh/
├── fullchain.pem   # Certificate + intermediate chain
├── privkey.pem     # Private key
├── cert.pem        # Certificate only
└── chain.pem       # Intermediate chain only
All three services reference these files. Certbot manages renewal automatically.

Dev paths

The Vagrant VM has no public domain, so Let’s Encrypt can’t issue certs. Instead:
FilePath
Certificate/etc/ssl/certs/molly-brown.crt
Private key/etc/ssl/private/molly-brown.key
Generated by the Gemini role as a self-signed EC key with SAN:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
  -keyout /etc/ssl/private/molly-brown.key \
  -out /etc/ssl/certs/molly-brown.crt \
  -days 3650 -subj "/CN=atl.sh" \
  -addext "subjectAltName=DNS:atl.sh"
The SAN extension is required because Go’s TLS library (used by molly-brown) rejects certificates with only a CN and no SAN. The private key is owned by root:ssl-cert with mode 0640. The FTP role’s dev override points to these same files.

Let’s Encrypt (certbot)

Initial issuance

Certbot runs during the first deploy via the nginx role:
certbot certonly --nginx --non-interactive --agree-tos \
  --email admin@allthingslinux.org -d atl.sh
This only runs when /etc/letsencrypt/live/atl.sh/fullchain.pem doesn’t exist. It’s skipped entirely on the dev VM.

Auto-renewal

The certbot.timer systemd timer is enabled by the nginx role. It runs twice daily and renews certificates that are within 30 days of expiry.
# Check timer status
systemctl status certbot.timer

# Check next renewal date
certbot certificates

# Force a dry-run renewal
certbot renew --dry-run

# Force actual renewal
certbot renew --force-renewal
After renewal, nginx needs to reload to pick up the new cert. Certbot’s deploy hook handles this automatically.

Renewal for non-nginx services

Molly-brown and vsftpd read the same Let’s Encrypt cert files. After certbot renews:
  • nginx reloads automatically (certbot deploy hook)
  • molly-brown and vsftpd need a restart to pick up new certs — add a post-renewal hook if needed:
# /etc/letsencrypt/renewal-hooks/post/restart-services.sh
#!/bin/bash
systemctl restart molly-brown@molly-brown
systemctl restart vsftpd

TLS hardening

nginx

Security headers are set globally:
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";

vsftpd

ssl_enable=YES
force_local_data_ssl=YES
force_local_logins_ssl=YES
ssl_tlsv1=YES
ssl_sslv2=NO
ssl_sslv3=NO
ssl_ciphers=HIGH
SSLv2 and SSLv3 are disabled. Only TLSv1+ with HIGH cipher suites are accepted.

Troubleshooting

“Certificate has expired”
certbot certificates          # check expiry dates
certbot renew --force-renewal # force renewal
systemctl restart nginx molly-brown@molly-brown vsftpd
“SSL: CERTIFICATE_VERIFY_FAILED” in dev
  • Dev uses self-signed certs — clients must be configured to accept them
  • For curl: curl -k https://localhost/
  • For Gemini clients: accept the TOFU (Trust On First Use) prompt
Certbot fails with DNS error
  • Verify DNS resolves: dig atl.sh
  • Certbot uses HTTP-01 challenge — port 80 must be open and reachable