Linux Server

Free SSL Without a Panel: Certbot and acme.sh on Nginx, Wildcard Certificates via Cloudflare DNS and Auto-Renewal

7 steps 15 min read 6 views 0
On this page

You don't need a control panel to run HTTPS. With Certbot or acme.sh you can get free Let's Encrypt certificates for any site on Nginx, issue wildcard certificates such as *.example.com through the Cloudflare DNS API, and renew them automatically. This guide covers both tools on Debian/Ubuntu and Rocky Linux/AlmaLinux, the Nginx HTTPS configuration, sites behind the Cloudflare proxy, and how to check that renewal really works.

Let's Encrypt certificates are valid for 90 days, and Let's Encrypt stopped sending expiry reminder emails in 2025. Always confirm automatic renewal with a dry run after setup, otherwise your site will break silently when the certificate expires.

Before You Start

  • The domain's A record (and AAAA record if you use IPv6) points to your server's IP. Check with dig +short example.com.
  • Ports 80 and 443 are open in the server firewall (see opening ports with firewalld and ufw).
  • Nginx is installed and already serves the site over HTTP.

Option A: Certbot with the Nginx Plugin (Easiest)

Certbot can request the certificate and edit your Nginx server block for you, including the HTTP to HTTPS redirect.

# Debian / Ubuntu
apt update
apt install -y certbot python3-certbot-nginx

# Rocky Linux / AlmaLinux (EPEL)
dnf install -y epel-release
dnf install -y certbot python3-certbot-nginx

# Request and install a certificate for the domain and www
certbot --nginx -d example.com -d www.example.com \
  --agree-tos -m [email protected] --redirect

# Renewal is scheduled automatically; confirm the timer and run a test renewal
systemctl list-timers | grep -i certbot
certbot renew --dry-run

Certificates are stored in /etc/letsencrypt/live/example.com/. If you prefer to keep your Nginx file untouched, use certbot certonly --nginx -d example.com and add the certificate paths yourself as shown in the Nginx HTTPS configuration below.

Option B: acme.sh (Lightweight, No Python)

acme.sh is a single shell script that runs as a cron job. Note that its default certificate authority is ZeroSSL; switch it to Let's Encrypt if you prefer.

# Install (creates ~/.acme.sh and a daily cron job)
curl https://get.acme.sh | sh -s [email protected]
source ~/.bashrc

# Use Let's Encrypt as the default CA
acme.sh --set-default-ca --server letsencrypt

# Issue using your site's web root (HTTP-01 validation)
acme.sh --issue -d example.com -d www.example.com -w /var/www/example

# Install the certificate where Nginx expects it and reload Nginx on every renewal
mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.crt \
  --reloadcmd      "systemctl reload nginx"

Never point Nginx directly at files inside ~/.acme.sh; always use --install-cert, which copies the files and runs the reload command after each renewal.

Wildcard Certificates with the Cloudflare DNS API

Wildcard certificates (*.example.com) require DNS validation. If your domain uses Cloudflare DNS, create an API token in the Cloudflare dashboard under My Profile → API Tokens using the "Edit zone DNS" template, limited to the zone you need.

# acme.sh: dns_cf reads the token from the environment and saves it for renewals
export CF_Token="your-cloudflare-api-token"
export CF_Zone_ID="your-zone-id"        # shown on the domain's Overview page in Cloudflare
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

# Certbot alternative
apt install -y python3-certbot-dns-cloudflare
mkdir -p ~/.secrets && chmod 700 ~/.secrets
echo 'dns_cloudflare_api_token = your-cloudflare-api-token' > ~/.secrets/cloudflare.ini
chmod 600 ~/.secrets/cloudflare.ini
certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

DNS validation also works when port 80 is closed or the site is not reachable yet, which makes it useful for internal services and new servers.

Nginx HTTPS Configuration

If you issued the certificate with certonly or acme.sh, add an HTTPS server block yourself. Adjust the certificate paths to the tool you used.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;                      # Nginx 1.25.1+; older versions: "listen 443 ssl http2;"
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;   # acme.sh: /etc/nginx/ssl/example.com.crt
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;     # acme.sh: /etc/nginx/ssl/example.com.key
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_session_cache shared:SSL:10m;

    root /var/www/example;
    index index.html index.php;
}
nginx -t && systemctl reload nginx

Sites Behind the Cloudflare Proxy

With the orange cloud enabled, HTTP validation normally still works because Cloudflare forwards /.well-known/acme-challenge/ requests to your server. Set the SSL/TLS mode in Cloudflare to Full (strict) once your origin has a valid certificate; "Flexible" sends traffic to your server unencrypted and often causes redirect loops. You can also use DNS validation, or a free Cloudflare Origin Certificate if the site is only ever served through Cloudflare. To stop visitors bypassing Cloudflare, see hiding your origin IP.

Check Expiry and Renewal

# Expiry date of the certificate your site actually serves
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

# Certbot: all certificates and their expiry
certbot certificates

# acme.sh: list certificates and force a test renewal
acme.sh --list
acme.sh --renew -d example.com --force

FAQ

Validation fails with "Timeout during connect" or a 404 error

Port 80 is blocked, the DNS record points somewhere else, or another server block answers for the domain. Check the firewall, run dig +short example.com, and make sure /.well-known/acme-challenge/ is not redirected or denied in your Nginx configuration. Otherwise switch to DNS validation.

I hit a rate limit while testing

Let's Encrypt limits how many certificates you can issue per week for the same domain names. Use --dry-run (Certbot) or --staging (acme.sh) while experimenting and only request a real certificate once the setup works.

Can I use one certificate for several domains?

Yes. Add each name with its own -d option. Wildcards cover only one level, so *.example.com matches shop.example.com but not a.shop.example.com or example.com itself; add the bare domain separately.

Still stuck after following these steps? Open a support ticket and the IMIDC 24/7 technical team will help. Include the server IP, OS version, your domain, the command you ran and the full error output so we can pinpoint the issue faster.

Was this answer helpful?

Related Tutorials