Skip to content
Back to skills

Lets Encrypt

FSecurity

Expert agent for Let's Encrypt free TLS certificates via ACME. Covers certbot, acme.sh, HTTP-01/DNS-01/TLS-ALPN-01 challenges, 90-day and 6-day certificate lifetimes, wildcard certificates, rate limits, staging environment, and automation best practices. WHEN: \"Let's Encrypt\", \"certbot\", \"acme.sh\", \"ACME\", \"free TLS\", \"LEGO\", \"Certify The Web\", \"short-lived certificate\", \"6-day certificate\", \"ACME challenge\", \"DNS-01 challenge\".

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 24, 2026
devopspythonrustgobashdockerawsazuretestinggitapi

Works with

  • cli
  • api

Security analysis

F32/100
  • criticalPipes output to a shell interpreter
  • mediumUses curl or wget to download content
  • highCreates or modifies cron jobs for persistent execution
  • highCreates or modifies cron jobs for persistent execution
  • criticalModifies startup scripts or system services for persistence
  • criticalDownloads and executes remote scripts — classic supply chain attack
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 24, 2026

npx -y skills add chrishuffman5/domain-expert --skill lets-encrypt --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Lets Encrypt?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Lets Encrypt
[![Security: F — Skills Directory](https://www.skillsdirectory.com/api/skills/chrishuffman5-lets-encrypt/badge)](https://www.skillsdirectory.com/skills/chrishuffman5-lets-encrypt)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: lets-encrypt
description: "Expert agent for Let's Encrypt free TLS certificates via ACME. Covers certbot, acme.sh, HTTP-01/DNS-01/TLS-ALPN-01 challenges, 90-day and 6-day certificate lifetimes, wildcard certificates, rate limits, staging environment, and automation best practices. WHEN: \"Let's Encrypt\", \"certbot\", \"acme.sh\", \"ACME\", \"free TLS\", \"LEGO\", \"Certify The Web\", \"short-lived certificate\", \"6-day certificate\", \"ACME challenge\", \"DNS-01 challenge\"."
license: MIT
---

# Let's Encrypt

This skill covers Let's Encrypt and the ACME protocol ecosystem. It has deep knowledge of certificate issuance, renewal automation, challenge types, rate limits, and integration patterns.

## How to Approach Tasks

1. **Identify the deployment context**: web server (nginx/Apache/Caddy), container (K8s), serverless, load balancer, or custom application.
2. **Determine challenge type needed**: HTTP-01 (single servers with port 80), DNS-01 (wildcard, no port 80 access), TLS-ALPN-01 (only port 443).
3. **Identify ACME client**: certbot (most common), acme.sh (scriptable), LEGO (Go), step CLI, or built-in (Caddy, Traefik).
4. **Consider lifetime**: 90-day (current default), 6-day (available, requires renewal every ~4 days), 45-day opt-in (May 2026).

## Certificate Lifetimes

| Profile | Lifetime | Renewal Frequency | Launched |
|---|---|---|---|
| Default (90-day) | 90 days | Every ~60 days (at 2/3) | Always |
| Short-lived (6-day) | 6 days | Every ~4 days | March 2025 |
| 45-day opt-in | 45 days | Every ~30 days | May 2026 |

**6-day certificates**: Require fully automated renewal. No OCSP needed (cert expires before compromise can be acted upon). Ideal for fully automated infrastructure.

**Recommendation**: Use 90-day unless you have full automation and want to eliminate revocation dependency.

## certbot

### Installation

```bash
# Ubuntu/Debian
sudo apt install certbot python3-certbot-nginx  # or python3-certbot-apache

# macOS
brew install certbot

# pip (any platform)
pip install certbot certbot-nginx certbot-apache

# snap (recommended for Ubuntu)
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
```

### Obtaining Certificates

```bash
# Nginx — automatically edits nginx config
sudo certbot --nginx -d example.com -d www.example.com

# Apache — automatically edits Apache config
sudo certbot --apache -d example.com -d www.example.com

# Standalone (temporary HTTP server on port 80)
# Use when nginx/Apache is not running
sudo certbot certonly --standalone -d example.com

# Webroot (place challenge files in existing webroot)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

# DNS-01 (for wildcard, requires DNS plugin)
sudo certbot certonly --dns-cloudflare \
    --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
    -d example.com -d "*.example.com"
```

### DNS-01 Plugins

certbot has plugins for major DNS providers:
- `certbot-dns-cloudflare` — Cloudflare
- `certbot-dns-route53` — AWS Route 53
- `certbot-dns-google` — Google Cloud DNS
- `certbot-dns-azure` — Azure DNS
- `certbot-dns-digitalocean` — DigitalOcean
- `certbot-dns-ovh` — OVH

```bash
# Install Cloudflare plugin
pip install certbot-dns-cloudflare

# Cloudflare credentials file
cat > ~/.secrets/certbot/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
chmod 600 ~/.secrets/certbot/cloudflare.ini

# Issue wildcard cert
certbot certonly \
    --dns-cloudflare \
    --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
    --dns-cloudflare-propagation-seconds 30 \
    -d example.com \
    -d "*.example.com"
```

### Certificate Files

Certbot stores certificates in `/etc/letsencrypt/live/<domain>/`:

```
/etc/letsencrypt/live/example.com/
  cert.pem         → Certificate only (not for nginx/Apache — use fullchain)
  chain.pem        → Intermediate certificate(s) only
  fullchain.pem    → cert.pem + chain.pem (use this for most servers)
  privkey.pem      → Private key (readable only by root)
```

nginx config:
```nginx
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
```

### Renewal

```bash
# Test renewal (dry run, no actual cert change)
sudo certbot renew --dry-run

# Force renewal (even if not yet due)
sudo certbot renew --force-renewal

# Renew specific certificate
sudo certbot renew --cert-name example.com

# Check renewal timer (systemd)
sudo systemctl status snap.certbot.renew.timer
# Or cron:
0 0,12 * * * root certbot renew --quiet
```

### Renewal Hooks

```bash
# Deploy hook — runs after successful renewal
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx

# Pre-hook — runs before renewal attempt
# /etc/letsencrypt/renewal-hooks/pre/stop-haproxy.sh
#!/bin/bash
systemctl stop haproxy

# Post-hook — runs after renewal attempt (success or failure)
# /etc/letsencrypt/renewal-hooks/post/start-haproxy.sh
#!/bin/bash
systemctl start haproxy
```

Or configure in `/etc/letsencrypt/renewal/example.com.conf`:
```ini
[renewalparams]
deploy_hook = systemctl reload nginx
```

## acme.sh

A pure Bash ACME client with broad DNS provider support (150+ providers).

```bash
# Install
curl https://get.acme.sh | sh -s email=admin@example.com

# Issue certificate (HTTP-01, webroot mode)
acme.sh --issue -d example.com -w /var/www/html

# Issue certificate (standalone)
acme.sh --issue -d example.com --standalone

# Issue wildcard (DNS-01, Cloudflare example)
export CF_Token="your-cloudflare-api-token"
export CF_Account_ID="your-account-id"
acme.sh --issue -d example.com -d "*.example.com" --dns dns_cf

# Install certificate to nginx
acme.sh --install-cert -d example.com \
    --cert-file /etc/nginx/ssl/cert.pem \
    --key-file /etc/nginx/ssl/privkey.pem \
    --fullchain-file /etc/nginx/ssl/fullchain.pem \
    --reloadcmd "systemctl reload nginx"

# List all certificates
acme.sh --list

# Force renew
acme.sh --renew -d example.com --force

# Auto-renewal is handled by cron (installed automatically)
# Check with: crontab -l | grep acme
```

### acme.sh with Let's Encrypt Staging

```bash
# Use staging to test without hitting rate limits
acme.sh --issue -d example.com --standalone --staging

# Switch from staging to production
acme.sh --issue -d example.com --standalone --server letsencrypt
```

## Built-in ACME Servers

### Caddy

Caddy automatically obtains and renews Let's Encrypt certificates:

```caddyfile
# Caddyfile
example.com {
    root * /var/www/html
    file_server
    # TLS is automatic — Caddy handles ACME
}

# With custom email
{
    email admin@example.com
}
```

For internal CAs:
```caddyfile
example.com {
    tls {
        ca https://step-ca.internal/acme/acme/directory
    }
}
```

### Traefik

```yaml
# traefik.yml
certificatesResolvers:
  letsencrypt:
    acme:
      email: admin@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web      # use HTTP-01 challenge
      # OR:
      dnsChallenge:
        provider: cloudflare  # use DNS-01 challenge
        delayBeforeCheck: 30

---
# Docker label on service
labels:
  - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
  - "traefik.http.routers.myapp.rule=Host(`example.com`)"
```

## Rate Limit Management

### Avoiding Limits

1. **Use staging for testing**: `--staging` flag; staging uses separate rate limit pool
2. **Reuse certificates**: Don't re-issue when cert is still valid; use `--force-renewal` sparingly
3. **Batch issuance**: Include all SANs in one certificate rather than separate certificates per domain
4. **Monitor usage**: `https://crt.sh/?q=example.com` shows issued certificates

### When You Hit Rate Limits

Let's Encrypt returns a `429 Too Many Requests` or specific error message indicating which limit was hit:
- "too many certificates already issued for exact set of domains" — Duplicate limit (5/week)
- "too many certificates already issued for this domain" — Domain limit (50/week)

**Only solution**: Wait until the sliding window resets (7 days from oldest cert in that group).

### Rate Limit Exceptions

Apply for rate limit increases: `https://issuance-limit-requests.letsencrypt.org` for legitimate high-volume needs. Alternatives:
- ZeroSSL (same ACME protocol, separate rate limits)
- Google Trust Services (separate rate limits, requires Google Cloud project)
- AWS Certificate Manager (ACM) for AWS-hosted workloads (no rate limits, free in ACM)

## Common Issues and Fixes

### "Connection refused" on HTTP-01 challenge

1. Check port 80 is open in firewall/security group
2. Check web server is listening on port 80
3. Check no redirect loop before challenge token is served: challenge path must serve token on HTTP (not redirect to HTTPS)
4. If behind load balancer: LB must forward `.well-known/acme-challenge/` to the server running certbot

```nginx
# Ensure challenge path is served before HTTPS redirect
server {
    listen 80;
    server_name example.com;
    
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
        # Served BEFORE redirect
    }
    
    location / {
        return 301 https://$host$request_uri;
    }
}
```

### "DNS problem" on DNS-01 challenge

1. Check DNS propagation: `dig TXT _acme-challenge.example.com @8.8.8.8`
2. Increase `--dns-cloudflare-propagation-seconds` (or equivalent) to 60-120s
3. Verify API token has DNS:Edit permission for the zone
4. Check TTL on TXT record (short TTL helps during validation)

### Certificate Not Renewed After Expiry

1. Check certbot timer: `systemctl status snap.certbot.renew.timer`
2. Check cron: `crontab -l | grep certbot` or `/etc/cron.d/certbot`
3. Check logs: `/var/log/letsencrypt/letsencrypt.log`
4. Verify certificate location: `certbot certificates`
5. Check if server is reloaded after renewal (missing deploy hook)

### CAA Record Blocking Issuance

If you have CAA records that don't include `letsencrypt.org`:
```
# DNS: Allow Let's Encrypt
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issuewild "letsencrypt.org"  # for wildcard
```

## Production Checklist

- [ ] Test in staging environment first
- [ ] Configure automatic renewal (cron or systemd timer)
- [ ] Set up reload hook (nginx/Apache reload after renewal)
- [ ] Monitor certificate expiry independently (external monitoring, not just certbot)
- [ ] Set up CAA DNS records
- [ ] Configure OCSP stapling in web server
- [ ] Test HTTPS with `https://www.ssllabs.com/ssltest/`
- [ ] Set up alerts for renewal failures (email or monitoring integration)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…