HowTo: SSL/TLS certificates with acme.sh

Note: this post is amended because the updated port security/acme.sh is now using its own convention home directory /var/db/acme with dedicated user/group acme:acme
The idea is to limit the use of elevated privileges as much as possible.

================
- What is this about?
security/acme.sh is a shell script with minimal dependencies to generate SSL/TLS certificates from Let's Encrypt https://letsencrypt.org/

It's the same philosophy as portmaster for managing FreeBSD's ports.
security/acme.sh is a shell script to manage SSL/TLS certificates.

Those certificates are fully functional and will not give any security warning like the self-signed certificates.

Note that https://freebsd.org/ uses "Let's Encrypt" and apparently even uses the security/acme.sh shell script.

- Why use security/acme.sh?
There is a large choice of tools to request certificates from Let's Encrypt but they all require many dependencies and root access.
security/acme.sh is a very minimalistic implementation of the ACME protocol which is used to automate the request and renewal of those SSL/TLS certificates.

- Installation:
pkg install security/acme.sh

- Requesting a certificate:

If you already have a web server running i.e. using port 80:
security/acme.sh needs to create a temporary subfolder under your web-directory called: .well-known
For this, we need to temporarily change the ownership of web-directory so that security/acme.sh can proceed with the change without any root priviledge.

Code:
# change ownership temporarily to user:acme group:acme
chown acme:acme /usr/local/www/your-web-site-dir

su acme
acme.sh --issue -d www.example.com -w /usr/local/www/your-web-site-dir

# restore ownership back to whatever user it was.
chown www:www /usr/local/www/your-web-site-dir

If you don't have any web server running on this host, you would need to run in standalone-mode:
make sure to pre-install net/socat since it is required for standalone mode.

Code:
su
pkg install net/socat

# run acme.sh as root because it needs to listen on port 80
acme.sh --issue -d www.example.com --home /var/db/acme --standalone

Notice a few things:
  1. We are requesting a certificate for www.example.com. Obviously, you need to change this to your own FQDN. It needs to resolve to your host and must be reachable from the Internet.
  2. The "--standalone" invocation needs to temporarily bind port 80 and thus needs to be run as user root
  3. The user running acme.sh, as defined by the port maintainer is "acme". This user is created automatically by the port or pkg.
  4. The "--home /var/db/acme" is also created by the port/pkg and is defined by the port maintainer as the default home for storing the scripts files and certificates.
  5. the "-w /usr/local/www/your-web-site-dir" option is where the script needs to write temporary data to be read from the certificate authority. It must be writable by the user executing the script in this case user "acme".
- Installing the certificate:
the acme.sh script creates a set of certificates:
Your cert is in /var/db/acme/www.example.com/www.example.com.cer
Your cert key is in /var/db/acme/www.example.com/www.example.com.key
The intermediate CA cert is in /var/db/acme/www.example.com/ca.cer
And the full chain certs is there: /var/db/acme/www.example.com/fullchain.cer

The acme.sh author recommends to use the --install-cert target to copy the certificates to the web server's locations:

Code:
# Create a place to store the certificate so that nginx/apache car read them:

mkdir -p /usr/local/etc/nginx/ssl/www.example.com/

touch /usr/local/etc/nginx/ssl/www.example.com/fullchain.cer

touch /usr/local/etc/nginx/ssl/www.example.com/www.example.com.key

touch /usr/local/etc/nginx/ssl/www.example.com/www.example.com.cer

chown -R acme:acme /usr/local/etc/nginx/ssl/www.example.com


# run the script as user “acme”

su acme

acme.sh --install-cert -d www.example.com \

--cert-file /usr/local/etc/nginx/ssl/www.example.com/www.example.com.cer \

--key-file  /usr/local/etc/nginx/ssl/www.example.com/www.example.com.key  \

--fullchain-file /usr/local/etc/nginx/ssl/www.example.com/fullchain.cer \

--reloadcmd     "sudo service nginx forcereload"

- Renewing the certificate:
The "Let's Encrypt" certificates have a short life span of 90 days and need to be renewed either manually or automatically and acme.sh script can take care of that:

Just create a cron job as the user running the script:
crontab -u acme -e
Code:
# Attempt to renew the certificates every once in a while:
45 1 * * * /usr/local/sbin/acme.sh --cron --home /var/db/acme/.acme.sh --reloadcmd "/usr/local/bin/sudo /usr/sbin/service nginx forcereload" > /dev/null

- This will attempt to renew the certificate every night. The script will check if the certificate is about to exipre and will renew it as necessary.

- It will restart the webserver (in this case nginx) if the certificate was renewed.

- security/sudo is used because we are running the cron job as a non root user and we need root priviledge to restart the webserver.
visudo
Code:
# add the following line to give access to user acme to restart a service
acme ALL=(root) NOPASSWD: /usr/sbin/service nginx forcereload

- Troubleshooting certificate creation:
The acme.sh has a few valuable knobs that you can add to it's invocation
--debug : displays detailed logs
--log : creates a log file with detailed logs normally found at /var/db/acme/.acme.sh/acme.sh.log
--staging: is a testing switch to dry-run certificate creation with LetsEncrypt
 
Last edited:
This appears to be the same as security/py-certbot. What is different or better?

I don't think I want something to be obtaining or updating certs for me not as root.

acme.sh doesn't require python on your system. If you don't have python on your system, you don't need to add it for acme.sh. That is why this is a suitable alternative. Of course, if you already have python on your server, then py-certbot is a good choice too.

Many of us use php or other server-side languages and don't require python on our servers.
 
It might have been better to edit your first post. Even so, I also want to comment that giving www access to sudo (as it's still shown in the original post) is an extremely bad idea. You're basically giving root permissions to everyone who has scripting access to any random website on that webserver instance. You might want to edit that part and remove it, because it's plain out bad advice.
 
It might have been better to edit your first post. Even so, I also want to comment that giving www access to sudo (as it's still shown in the original post) is an extremely bad idea. You're basically giving root permissions to everyone who has scripting access to any random website on that webserver instance. You might want to edit that part and remove it, because it's plain out bad advice.

It is also a bad idea to give the user that the web server runs as (typically www) write access to the documents that are served by the web server. If a hacker happens to find a way to write arbitrary files to your web server, he/she/it can replace your entire website if he/she/it can write to the files in the document root. It's almost as bad as giving the www user sudo access.

Since the acme protocol works by creating a $docroot/.well-known directory, you can create said directory and make it owned by the acme user to further restrict what can be modified by that script.

Personally, I wrap the acme.sh script in my own script. My script runs as root, switches to the acme user to run the acme.sh script, and then reboots the web server (as root). This way neither the acme nor the www user need sudo access.
 
I agree that giving sudo access to www is not a good idea that’s why I started my post with a request for comments before I update the original post.

With the new method (post #3), there are still two things that are making me uncomfortable:

- I am changing ownership of the $docroot web directory to user:acme before executing the acme.sh script.
I’m changing it back to its original owner www:www but still, this feels kind of wrong.
The idea is to avoid giving root access to acme.sh script but still be able to create $docroot/.well-known
I tried to pre-create the .well-known directory but acme.sh is overzealous and chown’s it back to the parent’s owner.

- In standalone mode, I am using a sudo because .acme.sh needs to temporarily listen on port 80. I thought of patching the acme.sh script like so:
< _NC="nc"
> _NC="sudo nc"
This way I would only limit sudo access to the netcat/socat command. I’m reluctant to do so because acme.sh is evolving rapidly and any port upgrade would change it back.

I am asking your comments before I go ahead and update the original post with the updated usage procedure.
 
I’ve just started to investigate this, and because I don’t install security/sudo on my systems as a rule. Instead of getting my acme user to run place the files correctly and restart (in my case) www/nginx, I get make it create an empty file; I then have a cron job run by root to check for the presence of this file and instead get root to simply move the file into place and then restart www/nginx. Nothing special is communicated via this file, root only checks that it is there.

Having said that, from using security/sudo on Linux boxen, when customers required additional privileges we would always get them to consider exactly what they required these additional privileges for and then only allow their user to use security/sudo for very specific commands - could you use this method to limit the damage a compromised www user could do?
 
Back
Top