Guide: Local PWS data into HA via Cloud Weather Proxy (Caddy + DNS spoofing)

Guide: Intercept your PWS data and feed Home Assistant locally with Cloud Weather Proxy + Caddy + Technitium DNS

TL;DR

Most personal weather stations (PWS) have hardcoded endpoints to rtupdate.wunderground.com that you cannot change. This guide shows how to intercept that traffic locally using DNS spoofing and a reverse proxy, so the data flows into Home Assistant via the cloudweatherproxy custom integration — without ever leaving your network.

Flow:
PWS → Local DNS (spoofed) → Caddy (reverse proxy) → Home Assistant (cloudweatherproxy)

What you need

  • Home Assistant (tested on HAOS 2026.5.x)
  • A local DNS server that allows overriding records (I used Technitium DNS, but Pi-hole / AdGuard Home / OPNsense / router-based DNS all work)
  • Docker host for Caddy (I used Portainer)
  • A PWS that publishes to Wunderground over plain HTTP (most cheap stations: Ecowitt, Sainlogic, Misol, Froggit, ICHILL, etc.). HTTPS stations won't work without extra effort.

Step 1 — Install Cloud Weather Proxy in HA

Via HACS:

  1. HACS → three-dot menu → Custom repositories
  2. Add https://github.com/lhw/cloudweatherproxy, category Integration
  3. Install "Cloud Weather Proxy"
  4. Restart Home Assistant
  5. Settings → Devices & Services → Add Integration → "Cloud Weather Proxy" and submit

The integration registers two HTTP routes on HA itself (port 8123):

  • /wunderground/weatherstation/updateweatherstation.php (Wunderground protocol)
  • /weathercloud/v01/set/{values} (Weathercloud protocol)

Both require authentication — the proxy will inject a Bearer token.

Step 2 — Create a Long-Lived Access Token in HA

Your user profile → bottom of the page → Long-Lived Access Tokens → Create → save it somewhere safe.

Step 3 — Allow the reverse proxy in HA

Edit configuration.yaml and add (using the IP where Caddy will run):

http:
  use_x_forwarded_for: true
  trusted_proxies:
    -  <IP of your Caddy host>

Restart HA.

Without this, HA rejects forwarded requests with a "request from a reverse proxy was received... but your HTTP integration is not set-up for reverse proxies" error.

Step 4 — DNS override in Technitium

In Technitium UI:

  1. Zones → Add Zone
  2. Name: rtupdate.wunderground.com, Type: Primary Zone
  3. Inside the zone, Add Record:
    • Name: leave empty (means the zone root)
    • Type: A
    • TTL: 60
    • IPv4 Address: IP of your Caddy host
  4. Save

Repeat for weatherstation.wunderground.com if your PWS uses it.

Verify from another machine on the network:

nslookup rtupdate.wunderground.com <technitium-ip>

Should return your Caddy host IP.

Important: make sure your DHCP hands out Technitium as the primary DNS to clients, or the PWS will keep resolving the real Wunderground IP via your ISP/router DNS.

Step 5 — Deploy Caddy via Portainer

In Portainer → Stacks → Add stack, name it cloudweatherproxy, paste:

services:
  caddy:
    image: caddy:2-alpine
    container_name: cwp-caddy
    restart: unless-stopped
    ports:
      - "80:80"
    environment:
      - HA_HOST=${HA_HOST}
      - HA_ACCESS_TOKEN=${HA_ACCESS_TOKEN}
    volumes:
      - caddy_data:/data
      - caddy_config:/config
    command:
      - sh
      - -c
      - |
        cat > /etc/caddy/Caddyfile <<EOF
        :80 {
            @wunderground host rtupdate.wunderground.com weatherstation.wunderground.com
            handle @wunderground {
                rewrite * /wunderground{uri}
                reverse_proxy http://${HA_HOST} {
                    header_up Authorization "Bearer ${HA_ACCESS_TOKEN}"
                }
            }
            handle {
                respond "cloudweatherproxy ok" 200
            }
        }
        EOF
        cat /etc/caddy/Caddyfile
        exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile

volumes:
  caddy_data:
  caddy_config:

Set environment variables in the stack form:

  • HA_HOST = <your-HA-ip>:8123 (e.g. 192.168.10.17:8123, no http:// prefix)
  • HA_ACCESS_TOKEN = your Long-Lived Access Token

Deploy.

What this Caddyfile does

  • Matches requests where Host: is rtupdate.wunderground.com or weatherstation.wunderground.com
  • Rewrites the path from /weatherstation/updateweatherstation.php?... to /wunderground/weatherstation/updateweatherstation.php?... (adding the /wunderground prefix that the integration expects)
  • Adds the Authorization: Bearer <token> header
  • Forwards to HA
  • Returns "cloudweatherproxy ok" on / so you can test it's alive

Step 6 — Test

Caddy alive:

curl http://<caddy-ip>/
# → cloudweatherproxy ok

Full flow simulated (using --resolve to skip your DNS):

curl -v --resolve rtupdate.wunderground.com:80:<caddy-ip> \
  "http://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?ID=TEST&PASSWORD=x&tempf=72&humidity=50&dewptf=52&action=updateraw&dateutc=now&softwaretype=test"
# → HTTP/1.1 200 OK
# → OK

If you get 200 OK, go to Settings → Devices & Services → Cloud Weather Proxy — you should see a new device appear with sensors populated from the test values (22.22°C from the 72°F you sent).

Step 7 — Point the PWS

The PWS should already be sending to rtupdate.wunderground.com. After all the above is in place:

  1. Power-cycle the PWS (most stations cache DNS in RAM until reboot — DNS changes won't apply otherwise).
  2. Wait for the next upload interval.
  3. Check Caddy logs in Portainer — you should see incoming requests from your PWS IP with status 200.
  4. The HA device will refresh with real values.

Troubleshooting

Caddy logs show 502 connection refused: HA is restarting or your HA_HOST is wrong. Check the value (no http://, just ip:port).

Caddy logs show 401 Unauthorized: token is wrong or expired. Generate a new one.

Caddy logs show 404 Not Found: the rewrite path is incorrect. Check it ends in /wunderground{uri} (no /api/ prefix, despite what some old docs suggest).

HA logs show "request from a reverse proxy was received... but your HTTP integration is not set-up for reverse proxies": you skipped Step 3 (trusted_proxies).

PWS keeps sending to real Wunderground: DNS cache. Power-cycle the PWS for a fresh lookup. Also verify the client really uses your Technitium as DNS (ipconfig /all on Windows, resolvectl status on Linux).

Need to discover the PWS's exact URL/parameters: watch Caddy logs after the PWS sends — the full URI will be there with the station ID, password, and all measurements.

Optional: also forward to real Wunderground

If you want the PWS to keep publishing publicly to Wunderground.com in parallel, Caddy needs to resolve the real IP. Add an upstream DNS to the Caddy container (so it doesn't ask Technitium, which would loop back):

services:
  caddy:
    # ...
    dns:
      - 1.1.1.1
      - 8.8.8.8

Then add a parallel reverse_proxy to the real Wunderground IP. (Out of scope here — leave a comment if interested.)


Credits to @lhw for the cloudweatherproxy integration. This guide assembles the full local setup that wasn't documented end-to-end anywhere.