-
-
Save davidbalbert/6815258 to your computer and use it in GitHub Desktop.
| ########################################### | |
| # IMPORTANT NOTE: | |
| # | |
| # As of asuswrt-merlin 380.67 Beta, you | |
| # can now configure SSL certificates from | |
| # the Webui, making these instructions | |
| # unnecessary. | |
| ########################################### | |
| # First, enable SSH in the Administration->System tab. | |
| # Then log in to the device. | |
| # Verify that https_crt_save is off | |
| admin@RT-N66U:/tmp/home/root# nvram get https_crt_save | |
| 0 | |
| # Enable https_crt_save and verify that it was set correctly | |
| admin@RT-N66U:/tmp/home/root# nvram set https_crt_save=1 | |
| admin@RT-N66U:/tmp/home/root# nvram get https_crt_save | |
| 1 | |
| # Write your custom key and certificate to the ephemeral file system. | |
| # Note that these files will not be preserved on restart. | |
| admin@RT-N66U:/tmp/home/root# cat >/etc/key.pem | |
| # paste in key | |
| admin@RT-N66U:/tmp/home/root# cat >/etc/cert.pem | |
| # paste in cert | |
| # Verify https_crt_file is empty | |
| admin@RT-N66U:/tmp/home/root# nvram get https_crt_file | |
| admin@RT-N66U:/tmp/home/root# | |
| # Restart httpd. When httpd starts up with https_crt_save enabled, it does the | |
| # following: If /etc/cert.pem and /etc/key.pem exist, it tars them together and | |
| # saves them in https_crt_file. If they do not exist (this would be the case | |
| # on reboot) and https_crt_file exists, httpd will extract the contents of | |
| # https_crt_file. You can see how this works in the start_ssl function here: | |
| # https://github.com/RMerl/asuswrt-merlin/blob/master/release/src/router/httpd/httpd.c | |
| admin@RT-N66U:/tmp/home/root# service restart_httpd | |
| # Ensure https_crt_file is now full | |
| admin@RT-N66U:/tmp/home/root# nvram get https_crt_file | |
| # ...snip... | |
| # Reboot AP to make sure cert is put back on boot | |
| admin@RT-N66U:/tmp/home/root# reboot |
I followed every variant posted here with the default firmware 3.0.0.4.388_24621 (deleting all .pem files, copying the custom .pem files, creating the cert.tgz manually in /jffs/, nvram commands in every order ...) – every time I restart the httpd service, the default Asus cert gets (re-)created automatically and overwrite my certs. nvram get https_crt_file never returns anything. :(
Maybe try updating your firmware? I have RT-AC58U with 3.0.0.4.382_52504 firmware and here is the full script that works for me:
#!/bin/sh
rm ~/router.sh
cat <<EOT >> router.sh
#!/bin/sh
cd /etc
ls *.pem
rm *.pem
nvram set https_crt_save=0
nvram unset https_crt_file
service restart_httpd
echo "httpd restarted"
nvram unset https_crt_file
service restart_httpd
echo "httpd restarted"
nvram get https_crt_file
sleep 20
ls *.pem
rm *.pem
nvram set https_crt_save=1
cat <<EOT >> cert.pem
EOT
cat /etc/certificates/new/letsencrypt.crt >> router.sh
echo "" >> router.sh
echo "EOT" >> router.sh
cat <<EOT >> router.sh
cat <<EOT >> key.pem
EOT
cat /etc/certificates/new/letsencrypt.key >> router.sh
echo "EOT" >> router.sh
cat <<EOT >> router.sh
rm server.pem
cat key.pem > server.pem
cat cert.pem >> server.pem
service restart_httpd
nvram get https_crt_file
EOT
cat router.sh | ssh -o StrictHostKeyChecking=no \
-p 4092 -i privatekey.pem admin@10.144.1.1
Unfortunately, 3.0.0.4.388_24621 is the latest firmware for my ZenWiFi AX (XT8) device, according to Asus' servers.
Something changed from some point after I wrote my comment above. But with what is current for my router (3.0.0.4.388_24329-g5906523) I was able to replace the certs.
I have this written down now.
mkdir tmp/etc
cd tmp
cat << EOF > etc/cert.pem
...
EOF
cat << EOF > etc/key.pem
...
EOF
tar -C / -czf /jffs/cert.tgz etc/cert.pem etc/key.pem
nvram set https_crt_save=1
service restart_httpd
Mostly, it looks like the difference is nvram set https_crt_save=1.
This is what I do (after creating cert.pem and key.pem in /jffs/):
cd /jffs/
rm cert.tgz
tar -czf cert.tgz cert.pem key.pem
nvram set https_crt_save=1
At this point nvram get https_crt_file returns nothing, and as soon as I execute service restart_httpd, the file cert.tgz gets overwritten by the system.
Maybe try updating your firmware? I have RT-AC58U with 3.0.0.4.382_52504 firmware and here is the full script that works for me:
...
If I execute the steps manually, this happens:
admin@ZenWiFi_XT8-A5C0:/tmp/etc# ls *.pem
cacert.pem cacert_gen.pem cakey.pem cakey_gen.pem cert.pem cert_gen.pem key.pem key_gen.pem server.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# rm *.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# nvram set https_crt_save=0
admin@ZenWiFi_XT8-A5C0:/tmp/etc# nvram unset https_crt_file
admin@ZenWiFi_XT8-A5C0:/tmp/etc# service restart_httpd
Done.
admin@ZenWiFi_XT8-A5C0:/tmp/etc# nvram unset https_crt_file
admin@ZenWiFi_XT8-A5C0:/tmp/etc# service restart_httpd
Done.
admin@ZenWiFi_XT8-A5C0:/tmp/etc# nvram get https_crt_file
admin@ZenWiFi_XT8-A5C0:/tmp/etc# sleep 20
admin@ZenWiFi_XT8-A5C0:/tmp/etc# ls *.pem
cacert.pem cacert_gen.pem cakey.pem cakey_gen.pem cert.pem cert_gen.pem key.pem key_gen.pem server.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# rm *.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# nvram set https_crt_save=1
admin@ZenWiFi_XT8-A5C0:/tmp/etc# cp /jffs/cert.pem .
admin@ZenWiFi_XT8-A5C0:/tmp/etc# cp /jffs/key.pem .
admin@ZenWiFi_XT8-A5C0:/tmp/etc# ll *.pem
-rw-rw-rw- 1 admin root 1249 Sep 3 08:12 cert.pem
-rw-rw-rw- 1 admin root 1704 Sep 3 08:12 key.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# cat key.pem > server.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# cat cert.pem >> server.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# ls *.pem
cert.pem key.pem server.pem
admin@ZenWiFi_XT8-A5C0:/tmp/etc# service restart_httpd
Done.
admin@ZenWiFi_XT8-A5C0:/tmp/etc# nvram get https_crt_file
admin@ZenWiFi_XT8-A5C0:/tmp/etc# ls *.pem
cacert.pem cacert_gen.pem cakey.pem cakey_gen.pem cert.pem cert_gen.pem key.pem key_gen.pem server.pem
/jffs/cert/tgz is recreated after all the steps above, but nvram get https_crt_file still returns nothing (and the router still presents the Asus certificate).
Yep, I've got an RT-AX88U Pro running Merlin 3004.388.8_2 and I can't replace the ss cert via the cli either.
I can manually update it via the web UI so I know my cert.pem and key.pem are good, but when I follow the steps suggested, i.e.
With etc/cert.pem etc/key.pem being my validated pem based cert and private key
steph@xxx:/tmp# tar -C / -czf /jffs/cert.tgz etc/cert.pem etc/key.pem
steph@xxx:/tmp# tar -tzf /jffs/cert.tgz
etc/cert.pem
etc/key.pem
steph@xxx:/tmp# nvram set https_crt_save=1
steph@xxx:/tmp# service restart_httpd
Done.
I see that /jffs/cert.tgz is overwritten with a server cert issued by a local CA on the router and the archive contents look like this:
steph@xxx:/tmp# tar -tzf /jffs/cert.tgz
etc/cacert.pem
etc/cakey.pem
etc/cert.pem
etc/key.pem
etc/cacert_gen.pem
etc/cakey_gen.pem
etc/cert_gen.pem
etc/key_gen.pem
steph@xxx:/tmp#
Maybe extract and retain the other files. The archive looks like this for me, which suggests in december I replaced the key and crt, but kept the rest. Perhaps if other files are missing it regenerates / restores it. I should have kept better notes...
tar tzvf cert.tgz
-rw-rw-rw- 0/0 1517 2018-05-05 01:05:31 etc/cacert.pem
-rw------- 0/0 1679 2018-05-05 01:05:31 etc/cakey.pem
-rw-rw-rw- 0/0 1838 2023-12-19 22:53:50 etc/cert.pem
-rw------- 0/0 1704 2023-12-19 22:53:51 etc/key.pem
-rw-rw-rw- 0/0 1517 2018-05-05 01:05:31 etc/cacert_gen.pem
-rw------- 0/0 1679 2018-05-05 01:05:31 etc/cakey_gen.pem
-rw-rw-rw- 0/0 1838 2018-05-05 01:05:25 etc/cert_gen.pem
-rw------- 0/0 1675 2018-05-05 01:05:25 etc/key_gen.pem
Yep, that was the missing piece of the puzzle.
Extracting the existing /jffs/cert.tgz into a temp subdir, overwriting etc/key.pem and etc/cert.pem and recreating /jffs/cert.tgz then running nvram set https_crt_save=1 and service restart_httpd seems to do the trick.
Finally !! Thanks for your help !
Yep, that was the missing piece of the puzzle.
Extracting the existing /jffs/cert.tgz into a temp subdir, overwriting etc/key.pem and etc/cert.pem and recreating /jffs/cert.tgz then running nvram set https_crt_save=1 and service restart_httpd seems to do the trick.
Finally !! Thanks for your help !
I tryed exactly this and checked the md5 sum:
Before copy cert.tgz to /jffs/cert.tgz md5 was: ef7ba5b1ec34074a56c1349653860d82
after copy: 899f0ec767378c8e5a84079d4e6315d3 /jffs/cert.tgz
then I ran following commands:
nvram set https_crt_save=1
service restart_httpd
after last command, the md5 was ef7ba5b1ec34074a56c1349653860d82 (as initial).
I'm using: 3.0.0.4.388_24328-g1e6e634
any idea?
after last command, the md5 was ef7ba5b1ec34074a56c1349653860d82 (as initial). I'm using: 3.0.0.4.388_24328-g1e6e634 any idea?
Same here. I am happy for everyone who managed to upload and activate their own certificate, but lost hope for myself.
I am using firmware 3004.388.9 and I'm having the same issue.
My sys log says this after i uploaded my own certificates:
Apr 26 18:58:45 httpd: Can't get basic constrain from /jffs/.cert/cert.pem
Apr 26 18:58:45 httpd: Delete uploaded certificate
Apr 26 18:58:48 admin: Waiting 5 seconds for VPN...
and ChatGPT says this:
this exact error:
httpd: Can't get basic constrain from /jffs/.cert/cert.pem
httpd: Delete uploaded certificate
means:
your uploaded SSL certificate is being rejected by the Asuswrt-Merlin firmware because it doesn’t contain a required X.509 extension called the Basic Constraints field.
Specifically, Merlin expects certificates to include:
✅ X509v3 Basic Constraints
✅ X509v3 Subject Key Identifier
✅ X509v3 Authority Key Identifier
If these aren't present exactly right, httpd (the web interface server) auto-deletes your uploaded cert.
✍ Why this happens:
Since 2024 and even stronger with firmware builds 3004.388.6 → 3004.388.9,
Merlin strictly enforces proper X.509 standards.
They're trying to block bad SSL setups that could crash or expose the router.
Old simple OpenSSL certs (without these extensions) get rejected now.
🛠 How you fix it:
You need to regenerate your certs with an openssl.cnf that includes:
bash
Copy
Edit
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
✅ This will make the router happy.
✅ It will keep your Root CA as is, no need to redo that.
✅ Only the server certs (e.g., for rt.datanovaconsulting.com) need updating.
I hope this clears things up for everyone concerning their custom certificates issues!!!
So my CA is using ECDSA with prime256v1. I'm guessing that's my issue here? I have an Asus ROG router and it just reverts back to auto when I upload my key and cert.
✅ X509v3 Authority Key Identifier
This was very helpful - thank you very much!
Root cause: missing Basic Constraints in the server cert (ASUS httpd requirement)
If you’re seeing Merlin fall back to the default RT-AX86U-XXXX Server Certificate even though your custom cert uploads successfully, the issue is not Merlin, not NVRAM, and not cert.tgz structure.
The issue is the X.509 extensions.
ASUS’s embedded httpd strictly requires the server certificate to explicitly contain:
X509v3 Basic Constraints:
CA:FALSE
If this extension is missing or not parsable, httpd will log something like:
httpd: Can't get basic constrain from /jffs/.cert/cert.pem
httpd: Delete uploaded certificate
Then silently reverts to the auto-generated ASUS certificate.
Cause of mkcert failure (Theory)
mkcert generates valid browser certificates, but some server certificates don't include an explicit basicConstraints = CA:FALSE entry in a format ASUS’s stripped-down OpenSSL parser accepts.
Fix: generate the router cert with OpenSSL & explicit extensions
Use OpenSSL directly for the router cert and explicitly declare Basic Constraints.
Example router-openssl.conf:
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[ dn ]
CN = router.lan
[ req_ext ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = router.lan
IP.1 = 192.168.1.1
Generate and sign with your local CA (mkcert’s CA works fine):
openssl genrsa -out router-key.pem 2048
openssl req -new -key router-key.pem -out router.csr -config router-openssl.cnf
openssl x509 -req \
-in router.csr \
-CA rootCA.pem \
-CAkey rootCA-key.pem \
-CAcreateserial \
-out router.pem \
-days 3650 \
-sha256 \
-extensions req_ext \
-extfile router-openssl.cnfYou can verify this by using:
openssl x509 -in router.pem -noout -text | grep -A4 "Basic Constraints"Expected Output:
X509v3 Basic Constraints:
CA:FALSE
Certificate & key requirements
| Requirement | Source |
|---|---|
| Certificate must be PEM | Implicitly supported by ASUS documentation |
| Must be RSA (not ECDSA) | Common community reports (e.g., GitHub, SNBForums) |
| Subject Alternative Name (SAN) required | Older guides and community usage assume SAN for hostname matching |
Basic Constraints: CA:FALSE |
Supported by community discussions and httpd rejection logs |
| X509v3 Subject Key Identifier (SKI) & Authority Key Identifier (AKI) | Explicitly mentioned by commenters in Merlin-related discussions |
Correct tar layout (/jffs/cert.tgz) |
Part of documented ASUSWRT-Merlin workflow |
NVRAM flags (https_crt_save=1) |
Known ASUSWRT-Merlin requirement |
Requirements are not Merlin-specific and are enforced by ASUS’s embedded httpd and OpenSSL build.
Merlin simply exposes the failure logs and allows persistence of custom certificates, which makes the issue debuggable.
Summary
- Not a Merlin bug
- Not an NVRAM or cert.tgz issue
- ASUS httpd enforces strict X.509 parsing
- Missing or malformed Basic Constraints causes silent cert rejection
- OpenSSL with explicit CA:FALSE resolves the issue permanently
Its been a while since I have been in the gist, but for anyone using a Lets Encrypt certificate this script below combined with the following acme.sh command gets a working internal certificate
acme.sh --home /jffs/acme.sh --issue -d example.com --dns dns_cf --debug --fullchain-file /etc/cert.pem --key-file /etc/key.pem --reloadcmd "/jffs/acme.sh/installcertificate.sh"/jffs/acme.sh/installcertificate.sh