Automating Let's Encrypt Certs for DD-WRT with 17 Jan, 2021 Comments Automating Let's Encrypt Certs for DD-WRT with

Following up on previous notes on setting up Let’s Encrypt for private networks and SSL for DD-WRT routers, here’s notes on automating renewal of Let’s Encrypt certificates using

I use Hurricane Electric’s free DNS service for delegating DNS management for the the private subdomains – DNS resolution is handled externally, but certificates are provisioned on the private network. You can find other free DNS providers on the Let’s Encrypt community page.

If you don’t want to make your private subdomain public, you can create a dummy.domain.ext, for example, and fetch wildcard certificates instead. If you are willing to open port 53 on your router and port forward, here’s another alternative. Open to more suggestions, please post them in the comments.

Setup Instructions

  • Prerequistites are a DD-WRT router with exeternal USB storage support. My notes on the setup are here and here.
  • First, delegate your subdomain’s public DNS resolution to by setting up NS records for subdomain.domain.ext to point to ns[1-5], then add the subdomain to via ‘Add a new domain’ page on the ‘Zone Functions’ menu.
  • Then, download, give it execute permissions: chmod +x ./
  • Next, run the following by adjusting the variables as appropriate.
export HE_Username=""
export HE_Password=""

./ --install  \
--home /jffs/etc/ \
--config-home /jffs/etc/ \
--cert-home  /jffs/etc/ \
--accountemail  "email@domain.ext" \
--useragent  "DD-WRT" --force
  • Then copy the corresponding dns_<provider>.sh from to /jffs/etc/
  • Test issuing a new cert: # /jffs/etc/ --issue --dns dns_he -d subdomain.domain.ext --config-home /jffs/etc/ 2>&1 >> /jffs/etc/ --staging --force
  • Revoke the test cert # /jffs/etc/ --revoke -d subdomain.domain.ext --revoke-reason 4 --config-home /jffs/etc/ --staging
  • Issue a production certificate by running the issue command above without --staging option.
  • Setup cron. Remember to prefix the cron command with root as pointed out here.
# Every day at 3 AM:*_*_*
0 3 * * * root /jffs/etc/ --renew --dns dns_he -d subdomain.domain.ext --config-home /jffs/etc/ >> /jffs/etc/ # --staging --force


The Best Test of Truth... 12 Jan, 2021 Comments The Best Test of Truth...

Reading Stratechery’s Tech and Liberty, ‘The Marketplace of Ideas’ section, I see this quote:

The theory of our Constitution is “that the best test of truth is the power of the thought to get itself accepted in the competition of the market,” Abrams v. United States, 250 U.S. 616, 630, 40 S.Ct. 17, 63 L.Ed. 1173 (1919) (Holmes, J., dissenting).

And, the first thing that jumps at me is the implication that Tuth, by this interpretation, is a point-in-time artifact. What’s marketable today is different from yesterday, and arguably, will change tomorrow; the need of each hour would vary, even if history repeats. I realize Justice Holmes is focused on the judgement at hand and this statement is completely accurate in that context.

This may be experience bias, but makes me wonder what would fall into the Vedic interpretation of truth – eternal, changeless, even if interpreted differently by experts: Ekam Sat, Viprah Bahudah Vadanti/एकं सद्विप्रा बहुधा वदन्ति - Rig Veda 1:164:46.

Or, may be I interpreted the above ancient statement as referring to Truth? This is open to interpretation: from the context of Hymn 164, the physical allusion is to a deity, but an overload of the word Sat is Truth, and therefore, the corresponding interpretation would be one of Absolute Truth, if I may. This is the interpretation I’ve grown up with.

What is Truth, to you?

Renewing Let's Encrypt SSL Certificates for Private Networks using DNS Challenge 05 Jan, 2021 Comments Renewing Let's Encrypt SSL Certificates for Private Networks using DNS Challenge

Note to self on setup for auto-renewing Let’s Encrypt SSL certificates from an (almost) private network.

The Usual Rant

This would’ve been really simple if Google Domains provided an API interface to add/update/remove DNS TXT records – and would be fully workable from a fully private network, I could (or maybe should?) use a DNS provider with an API and be done, but this setup is worth documenting for myself. For DNS providers with APIs to change DNS records, Certbot’s DNS plugins are, IMO, easy ways to renew certs. You could also go manual with your own auth, cleanup, and deploy hooks1. Anyway, here goes.

The Idea

The idea behind this setup is that of the Standalone DNS Authenticator Plugin for Certbot – I just could not get it to work2 out of the box even with port forwarding enabled, so I improvise by replicating the setup. Basically: send DNS resolution requests off to a DNS that you can update really quick (or even better, automate). This is achieved by inserting an NS record for the subdomain in question and pointing _acme-challenge.subdomain to a dummy.subdomain.domain.ext CNAME that sends off resolvers to your new DNS, where you work your magic.


  • A DNS provider with no API support to add/update/remove DNS TXT records.
  • A local/remote instance of AdGuardHome v0.105.03 or later – this nifty application has a REST API with a swagger spec!
  • An internet router that allows port forwarding (forward 53 to your local server hosting AdGuardHome)4 1.
  • Local server with certbot on a private network.

The Setup

For each subdomain of domain.ext, set up these two DNS entries:

  • subdomain NS ns.domain.ext
  • _acme-challenge.subdomain CNAME dummy.subdomain.domain.ext

Pro tip: always use –staging servers else you will run up against the really low (~5 failures/hour) rate limits for production servers.

  • Next, run the certbot command to renew certificate manually: # certbot --manual --preferred-challenges dns certonly -d subdomain.domain.ext --verbose --debug-challenge --staging
  • Go to AdGuardHome > Filters > Custom Filtering Rules and add appropriate entries
    • ||subdomain.domain.ext^$dnsrewrite=NOERROR;A;W.X.Y.Z (private IP is acceptable)5
    • ||_acme-challenge.subdomain.domain.ext^$dnsrewrite=NOERROR;TXT;challenge-txt, where challenge-txt is from the output of certbot command above.
  • Continue the command above, your certificates from staging Let’s Encrypt’s servers would be ready.
  • Revoke these test certs with # certbot revoke --cert-path /etc/letsencrypt/live/subdomain.domain.ext/cert.pem [--staging]
  • Repeat the certbot command without the --staging flag.

Best part – all of this can be automated.1 6

References & Notes

  1. This renewal can be automated by using auth- and deploy- hooks. Example commands certbot -d subdomain.domain.ext --agree-tos --manual --preferred-challenges dns --manual-auth-hook ./ --manual-cleanup-hook ./ --manual-public-ip-logging-ok --force-renewal certonly  2 3

  2. For my record, this command did not work: certbot --non-interactive --agree-tos --email certmaster@domain.ext certonly --preferred-challenges dns --authenticator certbot-dns-standalone:dns-standalone --certbot-dns-standalone:dns-standalone-address= --certbot-dns-standalone:dns-standalone-ipv6-address=:: --certbot-dns-standalone:dns-standalone-port=53 -d subdomain.domain.ext --force-renewal --staging --verbose --debug-challenges 

  3. AdGuardHome v0.105.0+ is required to support dnsrewrite 

  4. By default, only allow allow:, and your public IP: curl -k Temporarily update firewall rules to allow out-of-network requests. This too can be automated via AdGuardHome’s REST API: GET /control/access/list and POST /control/access/set in combination with ufw or similar. 

  5. I ran into SERVFAIL looking up CAA for ... error from certbot. Turns out the problem was a missing A record for subdomain.domain.ext in AdGuardHome, which is where the need for this entry originates from. was very helpful deduce this problem. It was on top of the list of DNS errors reported! 

  6. GET /status, add/update corresponding dnsrewrite entry into user_rules[] and POST /control/filtering/set_rules can be used in conjunction with the auth- and deploy- hook scripts to automate the entries that get inserted for challenge-txt

Let's Encrypt SSL Certificates for DD-WRT 05 Jan, 2021 Comments Let's Encrypt SSL Certificates for DD-WRT

Note to self on setting up Let’s Encrypt SSL certificates for DD-WRT.

Prerequisites: a router with DD-WRT with a USB port (to mount external USB drive via jffs), an external USB drive, and a domain to assign to your router. My notes on the router setup are here.

  • Create or renew the necessary certificate.
  • openssl rsa -in privkey.pem -out key.pem
  • scp *.pem root@router:/jffs/etc/
  • Router settings > Administration > Web Access > Protocol > HTTPS (check this box)
  • Router settings > Administration > Remote Access > Use HTTPS (check this box), and set Web GUI Port to 443
  • Save, Apply Settings, and Reboot Router (for good measure).
  • Copy to /jffs/etc/ and run this script, following instructions from this gist

Effectively, this mount-binds the corresponding cert.pem, and key.pem into /etc/*.pem followed by servicestart httpd.

Worked like a charm!


Airprint with CUPS on DD-WRT 27 Jul, 2020 Comments Airprint with CUPS on DD-WRT

Another note to self on enabling Airprint for a legacy network printer with Avahi and CUPS on DD-WRT (HP P3005).

Prerequisites: router with DD-WRT and a USB port (to install cupsd via Entware), a network capable printer. My notes on the router setup are here.

Mostly a rehash of the tutorials from TomatoUSB1 and EzUnix2, in case those links go dead:

  • opkg install hplip-full
  • opkg install cups cups-filters cups-pdf # this was required to get CUPS to recognize application/pdf as valid filter and to enable pdftoraster needed below.
  • Add this to Firewall starup script:

      # CUPSd WebInterface (http/tcp)
      iptables -I INPUT -p tcp --dport 631 -i vlan2 -j ACCEPT
      # CUPSd printer port
      iptables -I INPUT -p tcp --dport 9100 -i vlan2 -j ACCEPT
  • Navigate to CUPS web interface http://< router-ip >:631 > Administration > Add Printer
    • Choose HP Printer (HPLIP), Continue
    • Connection: socket://< network-printer-ip >:9100, Continue
    • Provide Name, Description, Location and choose to ‘Share This Printer’, Continue
    • Choose Make: HP, Continue
    • Choose Model: HP LaserJet p3005…, Add Printer
  • Run:

      echo "image/urf application/pdf 100 pdftoraster" > /opt/share/cups/mime/airprint.convs
      echo "image/urf urf string(0,UNIRAST<00>)" > /opt/share/cups/mime/airprint.types
  • Create /opt/etc/avahi/services/airprint-hpp3005.service with the following content 3,4:

      <?xml version='1.0' encoding='UTF-8'?>
      <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
        <name replace-wildcards="yes">AirPrint HP1220C @ %h</name>
          <txt-record>note=your note</txt-record>
          <txt-record>product=(GPL Ghostscript)</txt-record>

    You could generate this file from TJ Fontaine’s airprint-generate, but that did not work for me with some Python errors I chose not to pursue.

  • /opt/etc/init.d/rc.unslung restart

Done, you should find this printer via Airprint. You can use the CUPS web interface to configure printer defaults (duplex, two-sided printing, economy/high quality etc.).

For the record, HP p3005 has a decent web interface at http://< printer-ip > and also at the CUPS port: http://< printer-ip >:631.


Original design for Tumblr crafted by Prashanth Kamalakanthan.
Adapted for Tumblr & Jekyll by Sai Charan. Customized theme available on Github.

Sai Charan's blog by Sai Charan is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Creative Commons License