OpenClaw: Enable HTTPS with a Self-Signed Certificate
OpenClaw enable HTTPS with a self-signed certificate: full openssl commands, nginx config, and every common handshake error decoded.
OpenClaw: Enable HTTPS with a Self-Signed Certificate
If you are standing up OpenClaw and you have hit a TLS error - a browser warning, a failed curl, or a handshake that just dies - you are in the right place. This is the copy-paste guide. Every command works, every common error is decoded below, and the nginx block is written specifically for how OpenClaw runs.
Self-signed TLS is the single biggest friction point when standing up an OpenClaw endpoint, and the confusion is worse in 2026. Meta is migrating its outbound WhatsApp API certificates to its own CA (April 2026), which has flooded search with broken-handshake questions from self-hosters who conflate two completely separate problems. Let’s untangle it, then fix it.
When You Actually Need a Self-Signed Certificate (and When You Don’t)
A self-signed certificate is one you issue yourself instead of buying or requesting one from a public Certificate Authority. The encryption is identical to what a paid cert gives you. The only thing missing is a trusted third party vouching that you are who you say you are.
That distinction decides everything:
A self-signed cert encrypts traffic perfectly well; what it lacks is a third party vouching for your identity - which is exactly what Meta’s webhook validator demands.
So here is the hard rule worth memorising: the WhatsApp Business API webhook will NOT accept a self-signed certificate. Meta’s validator requires a publicly trusted CA chain. If your goal is a production WhatsApp webhook, stop here and use the Let’s Encrypt path in our VPS install guide instead. Self-signed will only waste your afternoon.
Self-signed is the right tool when you are doing local development, running an internal-only tool on a LAN, or serving OpenClaw on a bare IP address where you cannot issue a public cert at all.
Decision Table: Which Certificate Type to Use
| Your setup | Use this cert | Why |
|---|---|---|
| localhost / dev machine | Self-signed | No public hostname to validate; trust it locally |
| Internal LAN tool | Self-signed | No public DNS; add cert to internal trust stores |
| IP-only host (no domain) | Self-signed | Public CAs will not issue for bare IPs |
| Public WhatsApp webhook | Let’s Encrypt (public CA) | Meta requires a publicly trusted CA chain |
| Any public-facing production site | Let’s Encrypt (public CA) | Browsers trust it with no warnings |
If your row says “Let’s Encrypt,” skip this guide and run Certbot. If it says “self-signed,” keep reading.
Generate the Certificate with OpenSSL (Copy-Paste Commands)
You only need OpenSSL, which ships on macOS and every Linux distro. Here is the one-liner that generates a 2048-bit key and a self-signed cert valid for 365 days:
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout openclaw.key \
-out openclaw.crt \
-days 365 \
-subj "/CN=openclaw.local"
What each flag does:
-x509- produce a self-signed cert, not a signing request-newkey rsa:2048- generate a fresh 2048-bit RSA key in the same step-nodes- “no DES,” meaning no passphrase, so nginx can start unattended-days 365- one year of validity (set whatever you want)-subj "/CN=..."- the Common Name, which must match your host
Match the Host Exactly or It Will Fail
The single most common reason self-signed certs fail is a CN or SAN mismatch. The name in the certificate has to match the exact host OpenClaw is served on. If you reach it at openclaw.local, the CN must be openclaw.local. If you reach it at 192.168.1.50, the cert has to cover that IP.
Modern clients increasingly ignore CN and check the subjectAltName (SAN) field instead, so the robust move is to always set a SAN. Create a small config file, san.cnf:
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = openclaw.local
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = openclaw.local
IP.1 = 192.168.1.50
Then generate the SAN cert with:
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout openclaw.key \
-out openclaw.crt \
-days 365 \
-config san.cnf -extensions v3_req
Now the cert covers both the domain and the IP, which kills off the hostname-mismatch error before it can happen.
Place the Files and Lock Down the Key
Move the files somewhere nginx can read and then lock down the private key so nothing but root can read it:
sudo mkdir -p /etc/ssl/openclaw
sudo mv openclaw.crt openclaw.key /etc/ssl/openclaw/
sudo chmod 600 /etc/ssl/openclaw/openclaw.key
sudo chmod 644 /etc/ssl/openclaw/openclaw.crt
The chmod 600 on the key is not optional. A world-readable private key is a serious leak, and some setups will refuse to start if the permissions are too loose.
Wire the Certificate into OpenClaw’s Nginx Reverse Proxy
OpenClaw runs behind nginx, listening on port 18789. The right pattern is to terminate TLS at nginx, not inside the Node process. Nginx handles the certificate and the encrypted connection, then proxies plain HTTP to OpenClaw on the loopback. This keeps your TLS config in one place and lets you swap certs without touching the app.
Here is the server block:
server {
listen 443 ssl;
server_name openclaw.local;
ssl_certificate /etc/ssl/openclaw/openclaw.crt;
ssl_certificate_key /etc/ssl/openclaw/openclaw.key;
# Force TLS 1.2+ and a sane cipher suite
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:18789;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
}
A few details that matter:
ssl_protocols TLSv1.2 TLSv1.3drops the ancient, broken versions of TLS so you are not exposing OpenClaw over a downgradeable handshake.- The
UpgradeandConnectionheaders keep WebSocket connections alive, which OpenClaw uses for live messaging. proxy_passtargets127.0.0.1:18789- loopback only, so the Node process is never exposed directly.
Test the config and reload nginx:
sudo nginx -t && sudo systemctl reload nginx
Then confirm OpenClaw answers over HTTPS. Because the cert is self-signed, pass it to curl so the check is real:
curl --cacert /etc/ssl/openclaw/openclaw.crt https://openclaw.local
If you get HTML back instead of a TLS error, you are done with the happy path. If you got an error, the next section decodes it.
Fixing the Common Self-Signed Errors
Self-signed certs throw a handful of errors that all sound scary and all have clean fixes. Here is each one, decoded.
curl: “SSL certificate problem: self signed certificate”
This means curl reached the server, got the cert, and could not trace it back to a trusted CA - which is expected for a self-signed cert. The wrong fix is curl --insecure (or -k), which disables verification entirely and hides genuine problems.
The right fix is to verify against your own cert:
curl --cacert /etc/ssl/openclaw/openclaw.crt https://openclaw.local
Now curl actually checks the certificate against the one you trust, so a real mismatch or expiry will still surface.
Browser: NET::ERR_CERT_AUTHORITY_INVALID
Your browser does not trust your homemade CA. To stop the warning on your own machine, add the cert to your OS or browser trust store:
- macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain openclaw.crt - Windows: double-click the
.crt, choose Install Certificate, select Local Machine, and place it in “Trusted Root Certification Authorities.” - Ubuntu / Debian:
sudo cp openclaw.crt /usr/local/share/ca-certificates/ && sudo update-ca-certificates
Restart the browser afterward. The warning disappears because your machine now trusts the issuer - which is you.
“certificate verify failed: hostname mismatch”
The name you connected to is not listed in the cert’s CN or subjectAltName. This is almost always because you reached the host by IP but only put a DNS name in the cert (or vice versa). The fix is to regenerate with the correct subjectAltName using the san.cnf block from earlier so the cert covers every name and IP you actually use.
openssl s_client: The Universal Debugging Command
When you cannot tell what is wrong, this command shows you the entire handshake:
openssl s_client -connect openclaw.local:443
Annotated, the output you care about looks like this:
subject=CN = openclaw.local <- the cert's identity (must match your host)
issuer=CN = openclaw.local <- same as subject means self-signed
Verify return code: 18 (self signed certificate) <- expected for self-signed
SSL-Session:
Protocol : TLSv1.3 <- confirms TLS version negotiated
A verify code of 18 (“self signed certificate”) or 19 (“self signed certificate in chain”) is normal here - it just means no public CA vouched for it. A subject that does not match your host, or a protocol older than TLS 1.2, points straight at your problem.
Verify the Whole Chain End-to-End
Before you rely on this setup, confirm the cert is actually what you think it is. Inspect the CN, SAN, and expiry directly:
openssl x509 -in /etc/ssl/openclaw/openclaw.crt -noout -dates -subject -ext subjectAltName
You will see the subject, the SAN entries, and the notBefore / notAfter dates. Check that the subject and SAN match your host and that the expiry is where you expect.
Next, test the OpenClaw endpoint locally with curl and your cert before you expose anything:
curl --cacert /etc/ssl/openclaw/openclaw.crt -v https://openclaw.local/
The -v flag shows the handshake so you can confirm the cert is served and accepted.
Do Not Forget the Expiry
A self-signed cert valid for 365 days expires in exactly 365 days, and nothing reminds you. Set a calendar reminder a week before, or script the regeneration so it renews itself. An expired cert produces the same “verify failed” errors and tends to hit at the worst possible time.
When to Graduate to Let’s Encrypt
The moment OpenClaw needs to be reachable by anything other than you and your own machines - a public webhook, a teammate’s laptop, Meta’s validator - it is time to graduate from self-signed to a public CA. Run Certbot for a free Let’s Encrypt cert and let it auto-renew. For locking down the rest of the deployment once TLS is in place, follow our OpenClaw security hardening guide.
If your end goal is WhatsApp specifically, remember that the webhook step needs a public cert from the start - the full sequence is in our OpenClaw WhatsApp setup guide.
Stuck on a Handshake Error? We Will Fix It Today
Self-signed TLS is fiddly, and a single character wrong in a SAN block can cost you an afternoon. If you are mid-install and blocked on a TLS handshake error, you do not have to grind through it alone.
Our team configures SSL on your OpenClaw instance the same day - certificate, nginx, the whole chain verified end-to-end. Book a free 30-minute consultation through the OpenClaw Personal Installation service, or just contact us and we will get you unblocked. Most setups are running with valid HTTPS within hours, not days.
Frequently Asked Questions
How do I enable HTTPS on OpenClaw with a self-signed certificate?
Generate a key and cert with openssl req -x509 -newkey rsa:2048 -nodes, set the CN and subjectAltName to your exact host, then point nginx at the files with ssl_certificate and ssl_certificate_key on a listen 443 ssl block that proxies to OpenClaw on port 18789. Terminate TLS at nginx, reload it, and confirm with curl using --cacert against your cert.
Why does OpenClaw reject my self-signed certificate for the WhatsApp webhook?
It is not OpenClaw rejecting it - it is Meta. Meta's webhook validator rejects self-signed certs because it requires a publicly trusted CA chain. A self-signed cert encrypts traffic perfectly well, but no third party vouches for your identity, so the WhatsApp Business API will not complete webhook verification. Use Let's Encrypt for the public webhook and keep self-signed for local testing only.
How do I fix 'SSL certificate problem: self signed certificate' with curl on OpenClaw?
That error means curl cannot verify the cert against a known CA. The correct fix is to pass your cert with curl --cacert /path/to/cert.crt https://your-host so curl actually verifies it. Avoid --insecure (-k) outside throwaway tests - it disables verification entirely and hides real problems like a hostname mismatch or an expired cert.
What is the openssl command to generate a self-signed certificate for OpenClaw?
Run openssl req -x509 -newkey rsa:2048 -keyout openclaw.key -out openclaw.crt -days 365 -nodes -subj "/CN=your-host". The -nodes flag skips passphrase encryption so nginx can start unattended, -days sets validity, and CN must match the exact host OpenClaw is served on. For domain plus IP coverage, add a subjectAltName extension via an openssl config block.
Does a self-signed certificate work for the WhatsApp Business API webhook?
No. The WhatsApp Business API webhook will not accept a self-signed certificate. Meta's validator demands a certificate signed by a publicly trusted Certificate Authority, so you need Let's Encrypt or another public CA for the production webhook endpoint. Self-signed certs are fine for localhost development, internal LAN tools, and IP-only setups that never touch Meta.
Ready for Your Personal AI Assistant?
Free 30-minute consultation. We'll assess your setup and recommend the right OpenClaw configuration for you.
Talk to an Expert