• Network
  • nginx
  • Reverse Proxy
  • Homelab

To:
M. P. van de Weerd
From:
Date:
Re:
A reverse proxy for the TL-WA901ND
Cc:
Public
Letterhead

A Reverse Proxy
for the TL-WA901ND

Do you want to secure your TP-Link TL-WA901ND wireless access point behind an nginx reverse proxy? (I mean, who doesn’t?) Then it’s worth knowing that there’s a trick to it. I’ve spent some time getting this to work properly, and to save others the hassle, I’m documenting my findings in this memo.

The first part of this memo describes the process I followed to arrive at a working configuration. The actual config is included at the end. The narrative is there mostly for context and rationale, so feel free to skip straight to the config if that’s what you’re here for.

Discussion

Let’s start with the setup. In my case, I’m running an nginx reverse proxy on a Raspberry Pi 5. I also host my own DNS for local traffic using Pi-hole on a Raspberry Pi 3B. The goal was to access the TL-WA901ND via HTTPS, using a custom (local) domain: wap.home.van-de-weerd.net. Sounds simple: create an A record on Pi-hole, generate an SSL cert, and write a basic nginx config.

Turns out, the TL-WA901ND does not appreciate being accessed via an unfamiliar hostname. While it does respond, it will redirect to a hostname it recognizes at the first opportunity. It prefers either its configured IPv4 address or tplinkap.net. Interestingly, this quirk became part of the eventual solution.

To get around this, the access point must be “tricked” into believing it’s being addressed as either its IP or tplinkap.net. This means rewriting the Host, Referer, and Origin headers in proxied requests. Other considerations include HTTP authentication, frames, and cookie behavior.

TP-Link is known for underwhelming web interfaces, and the TL-WA901ND is no exception. Navigation often redirects to the IPv4 address or tplinkap.net, both of which are hardcoded in the web GUI source. These references must be rewritten to use the custom domain, which can be achieved using nginx’s sub_filter directive. I applied filtering to plaintext, HTML, CSS, and JavaScript files. Since we’re using HTTPS, references to http://tplinkap.net are rewritten first, followed by scheme-agnostic replacements.

Initially, I chose to spoof the IP address as the hostname. However, this made it impossible to change the access point’s IP via the GUI, since the IP itself was being rewritten by the filter. Switching to tplinkap.net preserved the ability to change the IP address without breaking the interface.

With all these considerations in mind, I created a configuration that allows secure access to the TL-WA901ND web interface via a custom domain consistent with the naming scheme I use for my homelab, using HTTPS. TP-Link certainly made this a challenge, but it was a fun exercise, and I learned a few useful tricks along the way. Feel free to reach out with improvements, questions, or comments!

Configuration

server {
    listen 443 ssl;

    # This domain scheme lets me use Let's Encrypt SSL certs for
    # *.home.van-de-weerd.net. The domain is reserved for local web GUIs.
    # Some services may also be exposed remotely.
    server_name wap.home.van-de-weerd.net;

    # SSL settings omitted for brevity
    # ssl_certificate [...]

    location / {
        # Custom internal domain for the device, not the web interface.
        # Using ".home.arpa" as per RFC 8375.
        proxy_pass http://tlwa901-01.home.arpa/;

        # Trick the access point into thinking it's accessed via tplinkap.net
        proxy_set_header Host    tplinkap.net;
        proxy_set_header Referer http://tplinkap.net/;
        proxy_set_header Origin  http://tplinkap.net/;

        # Allow frames from same origin
        proxy_hide_header  X-Frame-Options;
        proxy_set_header   X-Frame-Options "SAMEORIGIN";

        # Pass through HTTP basic auth header
        proxy_set_header Authorization $http_authorization;

        # Adjust cookie path for domain mismatch
        proxy_cookie_path / "/; SameSite=None; Secure";

        # Replace hardcoded domain references in content
        sub_filter_once off;
        sub_filter_types text/plain text/html text/css application/javascript;
        sub_filter  "http://tplinkap.net" "https://wap.home.van-de-weerd.net";
        sub_filter         "tplinkap.net"         "wap.home.van-de-weerd.net";
    }

    # Provide a favicon to prevent 500 errors from missing requests
    location = /favicon.ico {
        alias /var/www/static/tplink.ico;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name wap.home.van-de-weerd.net;

    location / {
        return 308 https://wap.home.van-de-weerd.net;
    }
}

This website collects statistical data in order to improve your user experience.

Privacy Statement