> ## 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.

# SSL/TLS & Certificates

> Certificate management, renewal, and TLS configuration across atl.sh services.

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

| Service              | Port | Cert source                                  | Path                            |
| -------------------- | ---- | -------------------------------------------- | ------------------------------- |
| nginx (HTTPS)        | 443  | Let's Encrypt                                | `/etc/letsencrypt/live/atl.sh/` |
| molly-brown (Gemini) | 1965 | Let's Encrypt (prod) / self-signed (dev)     | See below                       |
| vsftpd (FTPS)        | 21   | Shared 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:

| File        | Path                               |
| ----------- | ---------------------------------- |
| 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:

```bash theme={null}
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:

```bash theme={null}
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.

```bash theme={null}
# 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:

```bash theme={null}
# /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:

```nginx theme={null}
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"**

```bash theme={null}
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
