Skip to content

Instantly share code, notes, and snippets.

@Greelan
Forked from lachesis/letsencrypt_notes.sh
Last active March 11, 2024 12:39
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save Greelan/28a46a33140b65c9a045573ca460f044 to your computer and use it in GitHub Desktop.
Save Greelan/28a46a33140b65c9a045573ca460f044 to your computer and use it in GitHub Desktop.
Set up Let’s Encrypt certificate using acme.sh as non-root user
# How to use acme.sh to set up Let's Encrypt, with the script being run
# mostly without root permissions
# See https://github.com/Neilpang/acme.sh for more
# These instructions use the domain "EXAMPLE.COM" as an example
# These instructions:
# - work on Ubuntu 18.04 and 20.04 with nginx
# - use CloudFlare DNS validation
# - set up a wildcard certificate for the "EXAMPLE.COM" domain
# - use a systemd service, rather than cron job, to renew the certificate
# When this is done, there will be an "acme" user that handles issuing,
# updating, and installing certificates. This user will have the following
# (fairly minimal) permissions:
# - Copy certificates and key to /etc/letsencrypt/EXAMPLE.COM
# - Reload your nginx server
# First things first - create a system user account and group for acme
sudo useradd -m -d /var/lib/acme -s /usr/sbin/nologin -r -U acme
sudo chmod 700 /var/lib/acme
# Create a directory for the acme account to save certs in
MYDOMAIN="EXAMPLE.COM"
sudo mkdir -m 710 /etc/letsencrypt/"$MYDOMAIN"
sudo chown acme.www-data /etc/letsencrypt/"$MYDOMAIN"
# Edit your sudoers file to allow the acme user to reload (not restart) nginx
sudo visudo
# Add the following line at the end:
acme ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx.service
# Now change to the "acme" user - you'll do most of the rest of this guide as them
sudo MYDOMAIN="$MYDOMAIN" -s -u acme bash
export HOME=/var/lib/acme
cd ~
# Install acme.sh
git clone https://github.com/Neilpang/acme.sh.git
cd acme.sh
./acme.sh --install
# Export your CloudFlare API token and account ID so that acme.sh can use them
# See https://github.com/Neilpang/acme.sh/wiki/dnsapi for more about API tokens
# You can find your account ID in the URL of any page within the Cloudflare Dashboard
# after selecting the appropriate account
export CF_Token="[insert]"
export CF_Account_ID="[insert]"
# Create your certificate
# Note that the "force" flag is needed for the below commands as otherwise
# the acme.sh script complains about being run as sudo
cd ~
.acme.sh/acme.sh --issue -d "$MYDOMAIN" -d *."$MYDOMAIN" --dns dns_cf --force
# If everything went well, install your certificate
.acme.sh/acme.sh --install-cert --domain "$MYDOMAIN" \
--ca-file /etc/letsencrypt/"$MYDOMAIN"/chain.pem \
--key-file /etc/letsencrypt/"$MYDOMAIN"/key.pem \
--fullchain-file /etc/letsencrypt/"$MYDOMAIN"/fullchain.pem \
--reloadcmd "sudo systemctl reload nginx.service" --force
# Disable default cron job as a systemd service will be created instead for renewal
.acme.sh/acme.sh --uninstall-cronjob --force
# Drop back to your own user
exit
# Now modify your nginx config to work with the new certs
sudo nano /etc/nginx/sites-enabled/"$MYDOMAIN"
# Example SSL config section
#server {
# ...
# ssl_certificate /etc/letsencrypt/EXAMPLE.COM/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/EXAMPLE.COM/key.pem;
# ssl_trusted_certificate /etc/letsencrypt/EXAMPLE.COM/chain.pem;
# include /etc/nginx/ssl/ssl_params; # Change to whatever SSL settings you use - see further below
# ...
#}
# acme.sh does not create its own suggested SSL settings for you to use with nginx,
# so you will need to create your own (if you haven't already)
# The following commands set up SSL parameters of a reasonable level of security -
# relax or harden as you see fit (eg to include OCSP stapling), or skip if you
# already have your own. See https://ssl-config.mozilla.org/ for suggestions
sudo mkdir /etc/nginx/ssl
sudo tee /etc/nginx/ssl/ssl_params >/dev/null <<EOF
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_ecdh_curve X25519:secp384r1;
ssl_session_tickets off;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
EOF
sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
# Now test nginx to see if everything is working
sudo nginx -t
# And reload if it worked
sudo systemctl reload nginx.service
# Create a systemd service and timer to renew the certificate and reload nginx
sudo tee /etc/systemd/system/acme_letsencrypt.service >/dev/null <<EOF
[Unit]
Description=Renew Let's Encrypt certificates using acme.sh
After=network-online.target
[Service]
Type=oneshot
User=acme
Group=acme
Environment="HOME=/var/lib/acme"
ExecStart=/var/lib/acme/.acme.sh/acme.sh --cron
SuccessExitStatus=0 2
EOF
sudo tee /etc/systemd/system/acme_letsencrypt.timer >/dev/null <<EOF
[Unit]
Description=Daily renewal of Let's Encrypt's certificates
[Timer]
OnCalendar=daily
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Now start and enable
sudo systemctl start acme_letsencrypt.timer
sudo systemctl enable acme_letsencrypt.timer
# Congrats, you have a Let's Encrypt wildcard certificate set up on your box
# and it is configured to automatically renew, all by running the acme.sh script mostly
# without root permissions (other than to reload nginx on renewal).
@PaulWebster
Copy link

Thanks for this.
I came across a problem when trying it in my environment. Here is what I found and how I solved it.
I do not know if this is a general problem - but have included a way to test for it.

The problem that I hit was that nginx was happily serving up https but some clients were reporting issues with certificate chain validation.
To show the problem I used:
openssl s_client -connect example.com:443 -servername example.com
If that does not report a problem then things are probably fine.
In my case it was reporting an issue.

My solution was to change the way that acme.sh was making the exported certs/key.
Instead of creating .cer files, I changed it to make .pem

.acme.sh/acme.sh --install-cert --domain EXAMPLE.COM
--key-file /etc/letsencrypt/EXAMPLE.COM/EXAMPLE.COM.pem
--fullchain-file /etc/letsencrypt/EXAMPLE.COM/fullchain.pem
--reloadcmd "sudo systemctl reload nginx.service" --force

and then configured nginx to use those 2 files rather than the 3 .cer files

@Greelan
Copy link
Author

Greelan commented Jan 16, 2020

Thanks for the feedback. So is this issue effectively what is the problem you found, ie you need a key file that is the combined domain cert and key? acme.sh doesn't appear to generate that in the first instance (ie, what is stores in /var/lib/acme/.acme.sh/EXAMPLE.COM is ca.cer, fullchain.cer, EXAMPLE.COM.cer, and EXAMPLE.COM.key), but is the effect of the modified --install-cert command you ran that it does that work for you, ie concatenates the cert and key and installs them as a pem?

@Greelan
Copy link
Author

Greelan commented Jan 16, 2020

Ah, I see I should have followed Neil's instructions more closely on this one. Will update the gist (and my installation!).

@Greelan
Copy link
Author

Greelan commented Jan 19, 2020

Gist has been updated, including to add the cert-file parameter, which will enable OCSP stapling to be used. Thanks again for the input.

@PaulWebster
Copy link

Excellent

@Greelan
Copy link
Author

Greelan commented Feb 14, 2020

Correction: should use ca-file parameter for OCSP stapling, although given ssl_certificate already uses fullchain.pem it is probably not even necessary now to use ssl_trusted_certificate. Gist updated to reflect modified parameter, as well as to update the SSL parameters to reflect that support for TLSv1.1 will be dropped by most browsers in March 2020.

@vivimncer
Copy link

Thank you! <3

@PaulWebster
Copy link

Back after over 2 years because of a fresh install that I have done.
I see that things have changed because of the underlying changes that have happened in acme.sh
Couple of extra steps that I had to do as a result.

acme.sh now using ZeroSSL by default (rather than LetsEncrypt) ... so a step is needed to set-up the ZeroSSL environment.
The approach taken depends on whether or not the user has a ZeroSSL account.
See https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA

Also - now using CloudFlare but probably worth mentioning that if using something else (I use Dynu) then the opening steps are different and user should follow the appropriate sequence from https://github.com/acmesh-official/acme.sh/wiki/dnsapi
remembering to also change the "--issue" command to use the correct "--dns" setting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment