DD-WRT Reverse Proxy and HTTPS (Asus RT-AC68, Pound, LetsEncrypt)

IoT devices on the home network rely on the router to be accessible from the internet. It functions as a remote proxy and translates between HTTPS used on the internet and HTTP on the local area network.

Reverse proxy messages
Reverse proxy messages

Reverse proxy

Wildcard CNAME record

A CNAME record maps a domain name to another domain name. In this case the “other” domain name is the DDNS name of our router. By using a wildcard CNAME, such as *.home.domain.com, we can match requests for non-existent sub-domain domain names to home.domain.com. If we would open https://ir.home.domain.com in a browser, the DNS server will give the IP address of home.domain.com to the browser. The browser will then sent an HTTP request to that IP address requesting the page ir.home.domain.com.

Add the CNAME records to your DNS configuration
home.domain.com   3600 IN CNAME dyndns.com
*.home.domain.com 3600 IN CNAME dyndns.com

Remember to replace domain.com with your domain name, and dyndns.com with the dynamic DNS address of your router.

Access Router (pound)

The wildcard CNAME is only part of the equation. Since we configured home.domain.com to map to the IP address of the access router, this router will need to forward HTTP requests to the real IR device on our home network. We do this using a reverse proxy.

Home router reverse proxy IoT
Reverse proxy

A reverse proxy uses the Host: field from the HTTP header, to determine where to forward the request to. This allows a server may use a single IP address or interface to accept requests for multiple DNS host names.

We choose the pound implementation because it seemed versatile. We installed pound on the /jffs partition, so that it will survive firmware updates. This guide is written for a Kong build of DD-WRT, but should work with any firmware that has access to lighttpd. You will need a JFFS partition and need to use command line tools via ssh/telnet.

To get up and running, we need the binaries, a script that starts it and configuration files. These files are available through this GitHub page. The scripts are based on Frater's post.

  1. Enable JFFS partition. From the router's GUI, enable JFFS (Administration » Management). On first use, check the box to "clean internal flash storage". Reboot as needed.
  2. Pound binaries. Our router's firmware didn't include pound binaries, so we copied it from an older firmware (kongac's r33010M)
    scp rtr2:/usr/sbin/pound rtr2:usr/sbin/poundctl /jffs/sbin/
    scp rtr2:/usr/lib/libssl.so.1.0.0 rtr2:/usr/lib/libcrypto.so.1.0.0 /jffs/lib
  3. Install startup script. Copy the script /jffs/sbin/pound.sh and its helper write_pound_cfg from GitHub.
  4. Configure port to listen on. The file /jffs/etc/pound/pound.pt1 specifies what port to listen at. The example below uses port 81 since port 80 is already in use for the DD-WRT UI.
    TimeOut     120 
    Alive       30 
    Control     "/tmp/pound.ctl" 
    
    ListenHTTP
        Address 0.0.0.0 
        Port 81
    
  5. Configure forward rules. The file /jffs/etc/pound/pound.pt2 specifies where to forward the HTTP request to. In the example below, we do route both on URL and Host.
            Service "ir-url"  # route home.domain.com/ir to 10.0.1.234:80
                    Url "/ir.*"
                    BackEnd 
                            Address 10.0.1.234
                            Port 80
                    End 
            End 
            Service "ir-host"  # route ir.home.domain.com to 10.0.1.234:80
                    HeadRequire "Host:.ir.home.domain.com.*"
                    BackEnd 
                            Address 10.0.1.234
                            Port 80 
                    End 
            End 
    End 
    
  6. Start pound using /jffs/sbin/pound.sh start
  7. Give it a spin by pointing a web browser to either
    • http://home.domain.com:81/ir
    • http://ir.home.domain.com:81/

Transport Layer Security

TLS icon

With a way to contact our IR device from the internet, let's secure the connection using TLS with a free certificates from Let's Encrypt by loosely following Neilpang's instructions. We use a slightly modified acme.sh script that fixes a mistake in the regular expression [ticket968] and to avoid problems with binding to a non-existing IPV6 address, we added -4 to the call to openssl. The modified script can be found here.

  1. Get CA certificates using opkg
    mkdir /jffs/opt; mount --bind /jffs/opt /opt  # mount /opt on /jffs/opt
    ( nvram get rc_startup; echo "mount --bind /jffs/opt /opt" ) \
     > /tmp/a; nvram set rc_startup="$(cat /tmp/a); nvram commit"
    bootstrap                                     # install opkg (for CA-certificates)
    opkg update; opkg install ca-certificates     # install CA certificates
  2. Make port 443 available. Disable the existing service on that port (Administration » Web Management » disable HTTPS for web access), and open port 443 on the firewall (Administration » Commands » Firewall » Edit; add the line "iptables -I INPUT 1 -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT").
  3. Try to request a certificate. Remember to replace domain.com with your domain name.
    cd /jffs/usr/ssl
    ./acme.sh --issue --tls -d home.domain.com \
      --home /jffs/usr/ssl --ca-path /opt/etc/ssl/certs \
      --pre-hook "/jffs/sbin/pound.sh stop" \
      --post-hook "/jffs/sbin/pound.sh start" --test

    If all goes well, remove the --test parameter and request the certificate for real.

  4. Automatically renew the certificate, by setting up a cron job to update the certificate before it expires (Administration » Management » Additional Cron Jobs).
    # sundays @2:02am, renew/install SSL certificates if necessary (restarting pound)
    2 2 * * 0 root /jffs/usr/ssl/acme.sh --cron --home /jffs/usr/ssl >/jffs/usr/ssl/cron.log 2>&1
    
  5. Create host.pem by Combine the SSL Private Key, the signed x.509 certificate, and all the certificates in the chain of trust in /jffs/etc/pound/host.pem so that pound can use them
    ./acme.sh --install-cert -d home.domain.com --home /jffs/usr/ssl \
      --cert-file /jffs/etc/pound/host.crt \
      --key-file /jffs/etc/pound/host.key \
      --fullchain-file /jffs/etc/pound/fullchain.crt \
      --reloadcmd "cat /jffs/etc/pound/host.key /jffs/etc/pound/host.crt \
                       /jffs/etc/pound/intermediate-ca.pem \
                     > /jffs/etc/pound/host.pem ; \
                   /jffs/sbin/pound.sh restart"
    
  6. Configure pound for HTTPS instead of HTTP, by modifying the configuration file /jffs/etc/pound/pound.pt1.
    TimeOut   120 
    Alive     30 
    Control   "/tmp/pound.ctl" 
    
    ListenHTTPS
        Address 0.0.0.0 
        Port 443
        Cert "host.pem"
    
  7. Restart pound using /jffs/sbin/pound.sh restart
  8. Give it a spin by pointing a web browser to either
    • https://home.domain.com/ir
    • https://ir.home.domain.com/
Embedded software developer
Passionately curious and stubbornly persistent. Enjoys to inspire and consult with others to exchange the poetry of logical ideas.

2 Replies to “DD-WRT Reverse Proxy and HTTPS (Asus RT-AC68, Pound, LetsEncrypt)”

  1. This is what I need but it seems cmplicated to update the firmware of the router. Do you know about any router that has reverse proxy support built in from start?

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.