Certificates with acme.sh (webroot) + permissions
In this section, we obtain TLS certificates for www.curvature.blog using acme.sh and webroot mode.
Goal: we can issue a cert successfully, and install the cert/key to a stable path for nginx (e.g. /etc/nginx/cert/).
Key idea: webroot mode requires nginx web root to be writable (at least temporarily) so ACME challenge files can be created.
3.1 Install acme.sh under a normal user
We run acme.sh under our non-root user (e.g. vpsadmin).
install
acme.shshellwget -O - https://get.acme.sh | shreload shell environment (so
acme.shcommand is available)shellsource ~/.bashrc(optional) upgrade acme.sh and enable auto-upgrade
shellacme.sh --upgrade --auto-upgrade
3.2 Choose CA (ZeroSSL vs Let's Encrypt)
At some point, acme.sh may default to ZeroSSL and ask for account email / EAB credentials.
We switched to Let's Encrypt for simplicity.
- set default CA to Let's Encryptshell
acme.sh --set-default-ca --server letsencrypt
3.3 Prepare Webroot Permissions (Crucial Step)
If /var/www/html is owned by root, acme.sh (running as vpsadmin) will fail to create the .well-known challenge directory, resulting in a Permission denied error.
- change ownership of webroot to allow
vpsadminto write - ensure nginx (
www-data) can still read it by setting permissions to755shellsudo chown -R vpsadmin:vpsadmin /var/www/html sudo chmod -R 755 /var/www/html
3.4 Issue certificate using webroot mode
webroot mode works like this:
- ACME server asks you to prove domain ownership
acme.shwrites a challenge file into<webroot>/.well-known/acme-challenge/...- ACME server fetches that file from your domain via HTTP (port 80).
This requires:
www.curvature.blogmust already point to your VPS IP.- nginx must already serve HTTP on port 80.
Issue the cert:
acme.sh --issue \
-d www.curvature.blog \
--webroot /var/www/htmlAfter success, the cert and key are temporarily stored under the acme.sh home directory (e.g., ~/.acme.sh/www.curvature.blog_ecc/).
3.5 Prepare Cert Directory
We want nginx to read certs from a stable system path: /etc/nginx/cert/
- create the directory if missingshell
sudo mkdir -p /etc/nginx/cert - grant
vpsadminownership of the directory soacme.shcan install files there (otherwise you will gettouch: cannot touch... Permission denied).shellsudo chown vpsadmin:vpsadmin /etc/nginx/cert
3.6 Install cert/key and setup Auto-Renew
Certificates renew automatically because acme.sh runs on a cron schedule. But nginx must be reloaded to pick up the updated cert files. We use --reloadcmd so that every renewal automatically reloads nginx.
- install the cert and define the reload command:shell
acme.sh --install-cert -d www.curvature.blog \ --key-file /etc/nginx/cert/www.curvature.blog.key \ --fullchain-file /etc/nginx/cert/www.curvature.blog.fullchain.cer \ --reloadcmd "sudo systemctl reload nginx"
⚠️ Edge Case (Re-running the command): If you ever need to re-run this install command after you have locked down the file permissions (Step 3.8), acme.sh will fail with cp: cannot open ... for reading: Permission denied. To fix this, you must temporarily give vpsadmin ownership of the files before installing again:
sudo chown vpsadmin:vpsadmin /etc/nginx/cert/www.curvature.blog.key /etc/nginx/cert/www.curvature.blog.fullchain.cer3.7 Allow passwordless nginx reload (Required for Cron)
Because --reloadcmd runs automatically in the background via cron under the vpsadmin user, it cannot prompt for a sudo password. We must explicitly allow vpsadmin to reload nginx without a password.
- edit the sudoers fileshell
sudo visudo - add this exact line to the bottom of the file:
vpsadmin ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
3.8 Restrict permissions again (Crucial for Security)
After issuing the cert and installing it to the nginx path, it is critical to restrict permissions so your private key isn't exposed.
Our final goal: Let vpsadmin own the directory (so acme.sh can overwrite files on renewal), but lock down the files so only root (and Nginx) can read the private key.
- apply the final security permissions:shell
sudo chown -R vpsadmin:vpsadmin /etc/nginx/cert sudo chmod 700 /etc/nginx/cert sudo chmod 600 /etc/nginx/cert/www.curvature.blog.key sudo chmod 644 /etc/nginx/cert/www.curvature.blog.fullchain.cer
3.9 Quick checklist for this stage
After finishing this section, you should have:
acme.shinstalled and configured for Let's Encrypt.webrootpermissions cleanly configured for ACME challenges.- A successfully issued certificate for
www.curvature.blog - Cert/key installed securely under
/etc/nginx/cert/ - Passwordless
sudoconfigured soacme.shcan automatically runsystemctl reload nginxupon renewal - File and directory permissions strictly tightened.