Complete Guide to Enabling HTTPS with acme.sh and Nginx
Overview
A practical HTTPS setup guide based on a real stellhub.top rollout, covering acme.sh, Let's Encrypt, Nginx, HTTP-01 validation, certificate installation, automatic renewal, and common troubleshooting.
This article is based on a real HTTPS rollout for
stellhub.top. It covers preparation from zero, certificate issuance, Nginx HTTPS integration, automatic renewal, and common error FAQ.
1. Solution Summary
Using acme.sh + Let's Encrypt + Nginx to enable HTTPS for a self-hosted website is fully feasible.
This solution is:
- Free
- Trusted by browsers
- Automatically renewable
- Suitable for personal websites, blogs, SaaS, self-hosted APIs, and microservice gateways
- Stable enough in engineering practice without purchasing commercial certificates
One point must be emphasized:
acme.sh is not a CA. It is only an ACME client.
The actual certificate issuers are:
- Let's Encrypt
- ZeroSSL
- Google Trust Services
As long as the certificate comes from these public CAs, mainstream browsers such as Chrome, Edge, Safari, and Firefox will trust it.
2. Final Target Architecture
The final access path is:
Browser
↓ HTTPS :443
Nginx
↓ HTTP :8080
ApplicationThat is:
- The HTTPS certificate is configured on Nginx
- Nginx performs TLS termination
- The backend application continues listening on a local HTTP port, such as
127.0.0.1:8080
This is the most common and most correct deployment approach.
3. Prerequisites
Using the domain stellhub.top as an example, the following conditions must be satisfied first.
3.1 Domain DNS Resolution Is Correct
Your domain needs to resolve to the server's public IP.
Check:
nslookup stellhub.top
nslookup www.stellhub.topOr:
ping stellhub.topConfirm that the resolved result is your server's public IP.
3.2 Cloud Server Security Group Allows Ports
The following ports must be open:
| Port | Purpose |
|---|---|
| 80 | ACME HTTP-01 validation |
| 443 | HTTPS access |
If you use Alibaba Cloud, Tencent Cloud, Huawei Cloud, AWS, or Google Cloud, allow these rules in the security group or firewall rules:
TCP 80 0.0.0.0/0
TCP 443 0.0.0.0/03.3 Local Server Firewall Allows Traffic
If firewalld is enabled:
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reloadIf it is not enabled, you can ignore this step.
4. Install Nginx
CentOS / Rocky / AlmaLinux:
yum install -y nginxStart Nginx and enable it on boot:
systemctl enable nginx
systemctl start nginxCheck status:
systemctl status nginxCheck listening ports:
ss -lntp | grep nginx5. Install acme.sh
Install:
curl https://get.acme.sh | shLoad environment variables:
source ~/.bashrcVerify:
acme.sh --versionSet the default CA to Let's Encrypt:
acme.sh --set-default-ca --server letsencrypt6. Step One: Configure Nginx for HTTP Validation First
Before issuing the certificate, Let's Encrypt needs to access your server through HTTP to verify that you really control the domain.
It will access a URL similar to:
http://stellhub.top/.well-known/acme-challenge/xxxxxTherefore, Nginx must first provide normal service on port 80.
7. HTTP Validation Configuration That Can Directly Replace /etc/nginx/nginx.conf
This is the minimum usable configuration for the certificate issuance phase.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name stellhub.top www.stellhub.top;
root /usr/share/nginx/html;
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
try_files $uri =404;
}
location / {
return 200 "acme ok\n";
}
}
}After saving, check:
nginx -tIf there is no problem, start or restart Nginx:
systemctl restart nginxTest HTTP:
curl http://stellhub.top
curl http://www.stellhub.topExpected response:
acme ok8. Test the ACME Validation Directory
Create a test file:
mkdir -p /usr/share/nginx/html/.well-known/acme-challenge
echo test > /usr/share/nginx/html/.well-known/acme-challenge/test.txtAccess it:
curl http://stellhub.top/.well-known/acme-challenge/test.txt
curl http://www.stellhub.top/.well-known/acme-challenge/test.txtExpected response:
testOnly after this step succeeds does acme.sh --issue --webroot make sense.
9. Issue the Certificate
Run:
acme.sh --issue -d stellhub.top -d www.stellhub.top \
--webroot /usr/share/nginx/htmlOn success, you will see output similar to:
Verifying: stellhub.top
Success
Verifying: www.stellhub.top
Success
Cert success.The certificate is generated by default under:
/root/.acme.sh/stellhub.top_ecc/The directory usually contains:
| File | Purpose |
|---|---|
stellhub.top.key | Private key |
stellhub.top.cer | Domain certificate |
ca.cer | Intermediate CA certificate |
fullchain.cer | Full certificate chain |
Nginx should ultimately use:
fullchain.cer
stellhub.top.key10. Do You Need to Save the Certificate Content Manually?
No.
The PEM content printed by acme.sh:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----does not need to be manually copied and saved.
The correct approach is to use acme.sh --install-cert to install the certificate into an Nginx-specific directory.
Do not let Nginx directly depend on /root/.acme.sh/....
Reasons:
/rootpermissions are not suitable for direct Nginx reads- The path is not suitable as a service runtime dependency
- It is inconvenient for unified management
--install-certcan bind a reload action after automatic renewal
11. Install the Certificate into the Nginx Directory
Create the certificate directory:
mkdir -p /etc/nginx/sslInstall the certificate:
acme.sh --install-cert -d stellhub.top \
--key-file /etc/nginx/ssl/stellhub.key \
--fullchain-file /etc/nginx/ssl/stellhub.cer \
--reloadcmd "systemctl reload nginx"Here, /etc/nginx/ssl/stellhub.cer is actually fullchain.cer.
When the certificate is automatically renewed later, acme.sh will automatically update these files and run:
systemctl reload nginx12. Final HTTPS Nginx Configuration
After the certificate is installed, you can update /etc/nginx/nginx.conf to the final version below.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name stellhub.top www.stellhub.top;
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name stellhub.top www.stellhub.top;
ssl_certificate /etc/nginx/ssl/stellhub.cer;
ssl_certificate_key /etc/nginx/ssl/stellhub.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}If your backend is not on 8080, change this line to the real port:
proxy_pass http://127.0.0.1:8080;If you temporarily do not have a backend application, you can first change it to:
location / {
return 200 "https ok\n";
}13. Apply the Final Configuration
Check the Nginx configuration:
nginx -tRestart Nginx:
systemctl restart nginxCheck ports:
ss -lntp | grep nginxYou should see:
:80
:443Test HTTPS:
curl -I https://stellhub.top
curl -I https://www.stellhub.topOpen in a browser:
https://stellhub.topSeeing the security lock means HTTPS has truly been enabled.
14. Automatic Renewal
Let's Encrypt certificates are usually valid for 90 days.
After installation, acme.sh automatically configures cron.
Check:
crontab -lYou will usually see:
~/.acme.sh/acme.sh --cron --home ~/.acme.shSimulate renewal manually:
acme.sh --cron --forceList certificates:
acme.sh --list15. FAQ: Issues Encountered in This Rollout
FAQ 1: acme.sh --issue Reports Connection refused
Symptom
Fetching http://stellhub.top/.well-known/acme-challenge/xxx: Connection refusedCause
Let's Encrypt failed to access your port 80.
This is not a certificate problem, nor an acme.sh command problem. It means the public HTTP service is not reachable.
Common causes:
- Nginx is not started
- Nginx is not listening on port 80
- The cloud server security group does not allow port 80
- The local firewall does not allow port 80
- The domain resolves to the wrong IP
Troubleshooting Commands
ss -lntp | grep ':80'
systemctl status nginx
curl http://127.0.0.1
curl http://stellhub.topSolution
Ensure that:
- Nginx is running normally
- Port 80 is being listened on
- TCP 80 is open in the security group
curl http://stellhub.topreturns content
FAQ 2: nginx -t Reports "server" directive is not allowed here
Symptom
nginx: [emerg] "server" directive is not allowed here in /etc/nginx/nginx.conf:1Cause
You placed:
server {
}directly at the top level of /etc/nginx/nginx.conf.
This is incorrect.
Nginx configuration has hierarchy:
main
├── events
└── http
└── server
└── locationserver {} must be written inside http {}.
Correct Form
http {
server {
listen 80;
}
}FAQ 3: nginx -s reload Reports /run/nginx.pid Does Not Exist
Symptom
nginx: [error] open() "/run/nginx.pid" failed (2: No such file or directory)Cause
Nginx is not running at all.
reload requires an existing Nginx master process.
Wrong Operation
nginx -s reloadwhen Nginx is not started.
Correct Operation
Check configuration first:
nginx -tThen start:
systemctl start nginxAfter configuration changes, use:
systemctl reload nginxIt is recommended to use systemctl consistently instead of mixing too many commands.
FAQ 4: Why Is the Browser Still Not HTTPS after Certificate Issuance Succeeds?
Cause
Successful certificate issuance only means the files have been generated.
You still need to:
- Install the certificate into the Nginx directory
- Configure
listen 443 ssl - Configure
ssl_certificate - Configure
ssl_certificate_key - Restart Nginx
Correct Process
acme.sh --install-cert -d stellhub.top \
--key-file /etc/nginx/ssl/stellhub.key \
--fullchain-file /etc/nginx/ssl/stellhub.cer \
--reloadcmd "systemctl reload nginx"Then configure:
server {
listen 443 ssl http2;
server_name stellhub.top www.stellhub.top;
ssl_certificate /etc/nginx/ssl/stellhub.cer;
ssl_certificate_key /etc/nginx/ssl/stellhub.key;
}FAQ 5: Should the Certificate Content Printed by acme.sh Be Saved Manually?
No.
Do not manually copy PEM text.
The certificate has already been saved to:
/root/.acme.sh/stellhub.top_ecc/For production use, install it with --install-cert into:
/etc/nginx/ssl/FAQ 6: Which Certificate File Should Nginx Use?
Use the fullchain.
In this solution:
ssl_certificate /etc/nginx/ssl/stellhub.cer;
ssl_certificate_key /etc/nginx/ssl/stellhub.key;where:
/etc/nginx/ssl/stellhub.cercomes from:
fullchain.cerDo not configure only the standalone domain certificate, or some clients may fail because the intermediate certificate chain is missing.
FAQ 7: Should HTTP Still Be Kept?
Yes.
Port 80 is recommended for two purposes:
- Redirect HTTP to HTTPS automatically
- Keep the validation entry for future ACME renewals
Recommended configuration:
server {
listen 80;
server_name stellhub.top www.stellhub.top;
location /.well-known/acme-challenge/ {
root /usr/share/nginx/html;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}FAQ 8: Do You Need to Buy a Certificate?
In most cases, no.
For:
- Personal websites
- Technical blogs
- SaaS
- Self-hosted admin consoles
- API gateways
Let's Encrypt is enough.
Scenarios that truly require commercial certificates are usually:
- Financial institutions
- Mandatory requirements from government or enterprise customers
- Special compliance requirements
- Enterprise brand-oriented OV / EV certificate needs
Otherwise, buying a certificate has low cost-effectiveness.
FAQ 9: Can You Issue a Certificate for an IP Address?
Usually no.
Public HTTPS certificates are generally issued for domain names.
Use:
stellhub.top
www.stellhub.topinstead of:
47.84.192.24FAQ 10: How Do You Issue a Wildcard Certificate?
If you later want to support:
*.stellhub.topyou need DNS-01 validation instead of HTTP-01.
Example:
acme.sh --issue -d stellhub.top -d '*.stellhub.top' --dns dns_cfThe exact command depends on the DNS provider, such as:
- Cloudflare
- Alibaba Cloud DNS
- Tencent Cloud DNSPod
- Route53
Wildcard certificates are more suitable for multi-subdomain scenarios, such as:
api.stellhub.top
grafana.stellhub.top
prometheus.stellhub.top
blog.stellhub.topFAQ 11: Does www.stellhub.top Need Separate Configuration?
Yes.
If the certificate request includes:
-d stellhub.top -d www.stellhub.topthen Nginx server_name should also include:
server_name stellhub.top www.stellhub.top;Otherwise, one domain may work while the other behaves unexpectedly.
FAQ 12: Why Is /root/.acme.sh Not Recommended as the Nginx Certificate Path?
Because it is acme.sh's internal working directory and is not suitable as a service runtime configuration path.
A more reasonable path is:
/etc/nginx/ssl/Benefits:
- Clear semantics
- More controllable permissions
- Easier backup
- Easier troubleshooting
- Suitable for multi-site management
FAQ 13: How Do You Confirm Nginx Is Really Listening on 443?
Run:
ss -lntp | grep ':443'If there is no output, the HTTPS server has not taken effect.
Continue checking:
nginx -t
systemctl status nginx
journalctl -u nginx -n 100 --no-pagerFAQ 14: Does a Stopped Backend Service Affect HTTPS?
It affects page access, but not the TLS handshake.
If Nginx is configured with:
proxy_pass http://127.0.0.1:8080;but no service is running on 8080, HTTPS access may return:
502 Bad GatewayThis means the HTTPS layer is working, but the backend application is not running.
For temporary testing, you can change it to:
location / {
return 200 "https ok\n";
}16. Recommended Final Command List
The full process can be condensed into the following commands.
# Install acme.sh
curl https://get.acme.sh | sh
source ~/.bashrc
# Use Let's Encrypt
acme.sh --set-default-ca --server letsencrypt
# Check nginx config
nginx -t
systemctl restart nginx
# Issue cert
acme.sh --issue -d stellhub.top -d www.stellhub.top \
--webroot /usr/share/nginx/html
# Install cert for nginx
mkdir -p /etc/nginx/ssl
acme.sh --install-cert -d stellhub.top \
--key-file /etc/nginx/ssl/stellhub.key \
--fullchain-file /etc/nginx/ssl/stellhub.cer \
--reloadcmd "systemctl reload nginx"
# Reload nginx
nginx -t
systemctl restart nginx
# Verify
curl -I https://stellhub.top
curl -I https://www.stellhub.top
Join the discussion
Comments are synchronized with GitHub Discussions in stellhub/stell-web.