TL;DRThe Type: unauthorized error means Certbot's HTTP-01 challenge failed โ Let's Encrypt couldn't fetch a temporary token file from http://yourdomain.com/.well-known/acme-challenge/. Run this first:
curl -I http://yourdomain.com/.well-known/acme-challenge/test
Connection refused? Your web server isn't running or port 80 is blocked. Got a 404? The server is up but something's intercepting the request path. 404 vs timeout tells you immediately which rabbit hole to go down.
What's Actually HappeningLet's Encrypt proves you own a domain through the HTTP-01 challenge: Certbot writes a short token file under /.well-known/acme-challenge/, and Let's Encrypt's servers reach out over plain HTTP to read it back. That exchange happens on Let's Encrypt's end โ you have no control over it once renewal starts. If the fetch fails for any reason, you get Type: unauthorized.
Six things cause this the vast majority of the time:
- Port 80 blocked โ firewall or cloud security group dropping inbound HTTP- Web server not running โ Nginx/Apache crashed, or a recent config change left it stopped- DNS mismatch โ the domain resolves to a different IP than this server- Server config intercepting
.well-knownโ a blanket HTTPS redirect catches the challenge request before it reaches the file- Cloudflare proxy โ the orange-cloud proxy mode sometimes terminates HTTP before the challenge token is served- CAA DNS record โ explicitly restricts which certificate authorities can issue certs for your domain## Step-by-Step Fixes### Step 1 โ Confirm port 80 is reachableRun this from a different machine (or use an online port checker like portchecker.co):
nc -zv yourdomain.com 80
If it times out or says refused, open the port:
# UFW (Ubuntu/Debian)
sudo ufw allow 80/tcp
sudo ufw reload
# firewalld (CentOS/RHEL)
sudo firewall-cmd --add-port=80/tcp --permanent
sudo firewall-cmd --reload
# iptables direct
sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT
Running on AWS, GCP, or Azure? The cloud security group sits outside the OS entirely. Open port 80 there too โ the console won't show it as blocked, but it is.
Step 2 โ Make sure your web server is running```
Nginx
sudo systemctl status nginx sudo systemctl start nginx
Apache
sudo systemctl status apache2 sudo systemctl start apache2
Check `journalctl -u nginx -n 50` if the service won't start โ a config syntax error from a recent edit is a common culprit.
### Step 3 โ Check DNS points to this server```
dig +short yourdomain.com
curl -s ifconfig.me
Both should return the same IP. If they don't, update the A record at your DNS provider and wait. With a TTL of 300 seconds, propagation takes 5โ10 minutes. With a 3600 TTL, budget an hour. Don't retry Certbot until they match.
Step 4 โ Fix Nginx blocking the challenge pathA blanket HTTPโHTTPS redirect will intercept the challenge before it ever touches the file. The fix is a location block that short-circuits the redirect for the challenge path only:
server {
listen 80;
server_name yourdomain.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
allow all;
}
location / {
return 301 https://$host$request_uri;
}
}
sudo nginx -t && sudo systemctl reload nginx
Step 4b โ Fix Apache blocking the challenge path```
<VirtualHost *:80> ServerName yourdomain.com
Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/
<Directory /var/www/html/.well-known/acme-challenge/>
Options None
AllowOverride None
Require all granted
</Directory>
Redirect permanent / https://yourdomain.com/
sudo apachectl configtest && sudo systemctl reload apache2
Step 5 โ Check CAA DNS records```
dig CAA yourdomain.com
No output? You're fine โ no CAA records means any CA can issue. If you do have them and `letsencrypt.org` isn't in the list, add it:
yourdomain.com. 3600 IN CAA 0 issue "letsencrypt.org"
### Step 6 โ Cloudflare usersCloudflare's orange-cloud proxy mode can terminate HTTP before your server serves the challenge token. The quickest fix: flip the DNS record to grey-cloud (DNS only) in the Cloudflare dashboard, wait 60 seconds, renew, then flip it back.
Rather not touch the proxy? Use the DNS-01 challenge with Cloudflare's API instead โ no HTTP involved at all:
sudo apt install python3-certbot-dns-cloudflare
sudo certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials ~/.secrets/cloudflare.ini
-d yourdomain.com
### Step 7 โ Run a dry run with verbose outputDon't go straight to a real renewal. Let's Encrypt rate-limits failed attempts (5 failures per domain per hour), so burn a dry run first:
sudo certbot renew --dry-run --verbose
The verbose output shows the exact URL Certbot tries to validate and what HTTP response it got back โ far more useful than the default one-liner error. Once the dry run passes cleanly, run the real thing:
sudo certbot renew
### Bonus โ Use standalone mode to isolate the problemWant to completely rule out your server config? Stop the server and let Certbot spin up its own temporary HTTP listener on port 80:
sudo systemctl stop nginx sudo certbot certonly --standalone -d yourdomain.com sudo systemctl start nginx
Works with standalone but fails normally? Your server config is the problem, full stop. DNS and firewall are both fine.
## Verify the Fix Worked```
# List all certificates and expiry dates
sudo certbot certificates
# Check the cert directly via OpenSSL
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null \
| openssl x509 -noout -dates
# Confirm HTTPS responds
curl -I https://yourdomain.com
The notAfter date should sit roughly 90 days out from today. If certbot certificates shows a future expiry, you're done.
Keep It From Breaking AgainPort 80 must stay open permanently โ even on sites that redirect everything to HTTPS. Let's Encrypt hits it every 60โ90 days for renewal. Block it and you'll be back here.
Next, confirm auto-renewal is scheduled:
# Check the systemd timer (modern Certbot installs)
systemctl list-timers | grep certbot
# Or check cron
crontab -l | grep certbot
Nothing shows up? Add a cron job manually:
sudo crontab -e
# Add:
0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
The --post-hook is worth noting: it only fires when a certificate is actually renewed, not on every run. Your server won't get an unnecessary reload at 3am on the 364 days a year nothing changes.

