ER605 v2 OpenWrt
Tested & verified — April 2026

Flash OpenWrt on
TP-Link ER605 v2

Complete guide: from stock firmware to a fully configured OpenWrt router with encrypted DNS, network-wide ad blocking, and secure NTP.

SoC
MT7621AT
RAM
128 MB
Target
ramips/mt7621
OpenWrt
25.12.2

i Overview

This guide covers replacing the stock TP-Link firmware on the ER605 v2 with OpenWrt. It applies to all hardware revisions (V2.0, V2.20, V2.26, V2.60) — the limiting factor is the firmware version, not the hardware revision.

Based on chill1Penguin's method. The password generator, backup scripts, and initramfs image come from that project.

Do NOT connect to the internet

Keep the WAN cable disconnected during the entire flashing process. If the router detects internet access, TP-Link requires an online-signed token and the locally generated password stops working.

No factory recovery after flashing

After flashing OpenWrt, the ER605's Emergency Recovery Mode is disabled. The only way back to stock firmware is via ubiformat from OpenWrt's console using your MTD backup.

Bricking risk

If power is lost during flashing, the router may be permanently bricked. Use a stable power source and don't disconnect anything during the process.

Firmware Compatibility

The hardware revision (V2.0, V2.20, etc.) does NOT matter. Only the firmware version matters.

Firmware Status Root Access Method
≤ 2.0.1 Works Direct root login with "root password"
2.1.1 – 2.2.5 Works GUI login + enable + debug + CLI debug password
2.2.6 Blocked Local password generator patched out
2.3.0 – 2.3.3 Blocked RSA-signed tokens + anti-rollback. Downgrade impossible.

Firmware 2.2.6+ is confirmed blocked. Tested live in March 2026: downgrade from 2.3.3 to 2.2.5 was impossible using Emergency Recovery, GUI rollback, SSH rollback, and factory reset. If your router already has 2.2.6+, your options are: contact TP-Link support, buy another ER605 with 2.2.5, or wait for a community bypass.

Password Generator

Generates root and debug passwords locally in your browser. Nothing is sent to any server. Only works with firmware ≤ 2.2.5.

Found on the label on the bottom of your ER605 or in System Status. Formats: AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, or AABBCCDDEEFF.

The username you use to log in to the web GUI.

Flash Guide

Estimated time: 30–45 minutes. Requirements: a Linux PC, an ethernet cable, ssh, curl, python3, md5sum.

Phase 1 — Preparation (with internet)

1

Download everything

Your PC needs internet for this step. Download all files before touching the router.

1a. Generate the debug password using the generator above.

Save both CLI debug passwords to a text file — you'll need the right one depending on your firmware version.

1b. Download flashing files:

mkdir -p ~/er605_flash
cd ~/er605_flash
curl -o er605v2_write_initramfs.sh https://raw.githubusercontent.com/chill1Penguin/er605v2_openwrt_install/main/er605v2_write_initramfs.sh
curl -o openwrt-initramfs-compact.bin https://raw.githubusercontent.com/chill1Penguin/er605v2_openwrt_install/main/openwrt-initramfs-compact.bin

1c. Download sysupgrade image:

# Sysupgrade 25.12.2 (latest stable):
curl -o openwrt-25.12.2-sysupgrade.bin https://downloads.openwrt.org/releases/25.12.2/targets/ramips/mt7621/openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin

1d. Verify you have everything:

ls -la ~/er605_flash/

You should see: er605v2_write_initramfs.sh, openwrt-initramfs-compact.bin, and the sysupgrade .bin file.

2

Disconnect internet & verify firmware

  1. Disconnect the WAN cable from the router
  2. Connect your PC to a LAN port via ethernet
  3. Go to http://192.168.0.1 and check the Firmware Version in System Status

If firmware is 2.2.5 or earlier → continue. If 2.2.6+ → you're blocked.

3

Enable SSH on the router

In the web GUI: System Tools → Diagnostics → Remote Assistance → enable it.

Phase 2 — SSH Access

4

Connect via SSH

The ER605's SSH server uses legacy algorithms that modern distros block by default. Pass them directly in the command:

ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa -c aes128-ctr [email protected]

Use your GUI username (usually admin). Password is the same as the GUI.

Note: scp does not work with the ER605 — the shell prints banners that corrupt the transfer. Use the HTTP server method in Step 8 instead.
5

Enter debug mode

Once inside SSH, run:

enable
debug

Paste the CLI debug mode password from Step 1. If it works, you'll have a root shell.

Phase 3 — MTD Backup (strongly recommended)

6

Back up all MTD partitions

Without this backup, restoring stock firmware requires soldering a UART serial console to the PCB. Don't skip this.
Option A: Network backup with netcat (recommended)

No USB needed. Two scripts handle everything: one runs on your PC and receives the data, the other runs on the router and sends it. Partitions are streamed directly without storing anything in the router's RAM.

Step 1 — Find your PC's IP:

ip -4 addr show | grep "192.168.0"

Note the IP (e.g. 192.168.0.109). You'll need it below.

Method 1: Simple scripts (one partition per connection)

receive_backup.sh — run on your PC:

#!/bin/bash
# receive_backup.sh — Run on your PC
# Receives all MTD partitions from the ER605 via netcat

DIR=~/er605_backup
mkdir -p "$DIR"

PARTITIONS="0:Bootloader 1:Config 2:Factory 3:firmware 4:panic-oops 5:partition-table 6:support-list 7:device-info 8:device-info.b 9:tddp 10:tddp.b 11:bootloader 12:kernel 13:rootfs 14:firmware-info 15:extra-para 16:log 17:rootfs_data 18:bootloader.b 19:kernel.b 20:rootfs.b 21:firmware-info.b 22:extra-para.b 23:log.b 24:rootfs_data.b 25:log_recovery 26:database"

for PART in $PARTITIONS; do
    NUM="${PART%%:*}"
    NAME="${PART##*:}"
    FILE="$DIR/mtd${NUM}_${NAME}.bin"
    echo ""
    echo "=== Waiting for mtd${NUM} (${NAME})... ==="
    nc -l -p 9090 > "$FILE"
    SIZE=$(stat -c%s "$FILE" 2>/dev/null || echo 0)
    echo "    Received: $FILE ($SIZE bytes)"
done

echo ""
echo "=== Backup complete ==="
ls -lh "$DIR"
If nc -l -p 9090 gives an error (common on Debian/Ubuntu with OpenBSD netcat), change it to nc -l 9090 without the -p flag.

send_backup.sh — run on the router:

#!/bin/ash
# send_backup.sh — Run on the ER605 router (ash/BusyBox)
# Usage: ash send_backup.sh YOUR_PC_IP

PC_IP="$1"

if [ -z "$PC_IP" ]; then
    echo "Usage: ash send_backup.sh YOUR_PC_IP"
    exit 1
fi

i=0
while [ -e "/dev/mtd${i}ro" ]; do
    NAME=$(grep "mtd${i}:" /proc/mtd | cut -d'"' -f2)
    echo ""
    echo "=== Sending mtd${i} (${NAME})... ==="
    dd if="/dev/mtd${i}ro" | nc "$PC_IP" 9090
    echo "    mtd${i} sent."
    sleep 2
    i=$((i + 1))
done

echo ""
echo "=== All partitions sent ==="

How to use:

  1. Transfer send_backup.sh to the router via the HTTP server: curl -o /tmp/send_backup.sh http://YOUR_PC_IP:8080/send_backup.sh
  2. Run ./receive_backup.sh on your PC (it waits for each partition)
  3. Run ash /tmp/send_backup.sh YOUR_PC_IP on the router
Method 2: chill1Penguin's scripts (with auto md5sum verification)

backup.sh — paste and run on the router:

Edit BACKUP_HOST to match your PC's IP, then paste this entire block into the router's shell:

cat << "EOF" > /tmp/backup.sh
#!/bin/sh

BACKUP_HOST="192.168.0.100"  # Your PC's IP address
BACKUP_PORT="9999"            # Port to use for transfer
echo "Backup host ${BACKUP_HOST}"

echo -n > /tmp/md5sums

cat /proc/mtd | tail -n+2 | while read; do
  MTD_DEV=$(echo ${REPLY} | cut -f1 -d:)
  MTD_NAME=$(echo ${REPLY} | cut -f2 -d\")
  FILENAME="${MTD_DEV}_${MTD_NAME}.backup"

  echo "Backing up ${MTD_DEV} (${MTD_NAME})"
  # First send filename
  echo "$FILENAME" | busybox nc ${BACKUP_HOST} ${BACKUP_PORT}
  sleep 1
  # Then send the data
  dd if=/dev/${MTD_DEV}ro | busybox nc ${BACKUP_HOST} ${BACKUP_PORT}
  SUM=$(dd if=/dev/${MTD_DEV}ro | md5sum | cut -f1 -d" ")
  echo -e "$SUM\t$FILENAME" >> /tmp/md5sums
  sleep 1
done

echo md5sums | busybox nc ${BACKUP_HOST} ${BACKUP_PORT}
dd if=/tmp/md5sums | busybox nc ${BACKUP_HOST} ${BACKUP_PORT}

EOF

chmod +x /tmp/backup.sh

receive.sh — save and run on your PC:

#!/bin/bash
# receive.sh — Run on your PC
# Receives MTD backups sent by backup.sh from the router

PORT=9999
DIR=~/er605_backup
mkdir -p "$DIR"
cd "$DIR"

while true; do
  # Receive filename
  FILENAME=$(nc -l -p $PORT)
  FILENAME=$(echo "$FILENAME" | tr -d '\r\n')
  if [ -z "$FILENAME" ]; then continue; fi
  echo "Receiving ${FILENAME}..."
  # Receive file data
  nc -l -p $PORT > "$FILENAME"
done
If nc -l -p gives an error, change to nc -l without -p.

How to use:

  1. Run ./receive.sh on your PC
  2. Run /tmp/backup.sh on the router
  3. Backup takes ~5 minutes. Don't interrupt it.
  4. When done, press Ctrl+C on the PC to stop receive.sh
  5. Verify: md5sum -c md5sums

Tip: mtd3 (firmware) is the largest (~130MB) and takes ~2 minutes. The rest are small and take seconds. Store these backups somewhere safe — they're your only way back to stock firmware without a serial console.

Original scripts from chill1Penguin/MTD_backup.

Option B: USB backup

Format a 256MB+ USB drive as FAT32, plug it into the router, mount it, and dump all partitions.

i=0; while [ -e "/dev/mtd${i}ro" ]; do
    NAME=$(grep "mtd${i}:" /proc/mtd | cut -d'"' -f2)
    echo "Backing up mtd${i} (${NAME})..."
    dd if="/dev/mtd${i}ro" of="/mnt/usb/mtd${i}_${NAME}.bin"
    i=$((i + 1))
done
cp /proc/mtd /mnt/usb/proc_mtd.txt
sync && umount /mnt/usb

Phase 4 — Transfer files to the router

7

Start a temporary HTTP server on your PC

cd ~/er605_flash
python3 -m http.server 8080
Firewall: If curl times out in the next step, your PC firewall may be blocking port 8080. Fedora: sudo firewall-cmd --add-port=8080/tcp. Ubuntu: sudo ufw allow 8080/tcp.
8

Download files from the router

cd /tmp
curl -o er605v2_write_initramfs.sh http://YOUR_PC_IP:8080/er605v2_write_initramfs.sh
curl -o openwrt-initramfs-compact.bin http://YOUR_PC_IP:8080/openwrt-initramfs-compact.bin
chmod +x er605v2_write_initramfs.sh
9

Verify checksum

md5sum openwrt-initramfs-compact.bin

The output must match:

e06dd6da68b739b2544931a0203292db openwrt-initramfs-compact.bin

Source: chill1Penguin/md5sums. If they don't match, do NOT continue.

Phase 5 — Flashing (point of no return)

10

Flash the initramfs image

Do NOT unplug or power off during this step. A power loss here bricks the router.
./er605v2_write_initramfs.sh openwrt-initramfs-compact.bin
11

Reboot

reboot

Wait 2–3 minutes. All LEDs will turn off, then blink, then stabilize. This is normal.

# Monitor from your PC:
while ! ping -c1 -W1 192.168.1.1 &>/dev/null; do echo "Waiting..."; sleep 2; done; echo "Router is up!"

Phase 6 — OpenWrt Setup

12

Access the installer

Open http://192.168.1.1 — note the IP changed from 192.168.0.1 to 192.168.1.1. You should see the "ER605 v2 Installer" page.

If it doesn't load: disconnect WiFi on your PC, wait longer, reconnect the ethernet cable, or try a static IP: sudo ip addr add 192.168.1.100/24 dev YOUR_INTERFACE.
13

Adjust UBI layout & flash sysupgrade 25.12.2

  1. Click "Adjust UBI Layout" if it says "NOT ADJUSTED"
  2. Upload the 25.12.2 sysupgrade image, uncheck "Keep settings", and click Flash
  3. Wait several minutes — do NOT power off the router
  4. After reboot, access http://192.168.1.1 → you should see LuCI. Login: root, password: empty.
If you see "Image check failed" in red, it's because "Keep settings" is checked. Uncheck it and the error disappears. Do NOT use "Force upgrade".

Post-Install Configuration

1. Change root password: System → Administration → Router Password
2. Configure SSH: System → Administration → SSH Access
3. Enable NAT offloading (critical for performance): Network → Firewall → Routing/NAT Offloading → enable "Software flow offloading". Without this, throughput drops ~50%.
4.
Set timezone:
uci set system.@system[0].timezone='YOUR_TZ_STRING'
uci set system.@system[0].zonename='Your/Timezone'
uci commit system

Change the values to match your timezone. See the OpenWrt timezone list for valid values.

5. Configure WAN: Network → Interfaces → WAN (now you can connect the WAN cable)
6.
Install useful packages:
ssh [email protected]
apk update
apk add luci-app-sqm           # Smart QoS
apk add luci-proto-wireguard   # Fast VPN
apk add luci-app-statistics collectd-mod-cpu collectd-mod-memory # Stats

Encrypted DNS (dnscrypt-proxy2)

All DNS queries from your network will be encrypted via DNSCrypt/DoH before reaching the internet. dnsmasq stays as local DNS, forwarding to dnscrypt-proxy on port 5353.

DNS chain
LAN devicesdnsmasq :53 + blocklistdnscrypt-proxy :5353Quad9 (encrypted)

Install

apk update
apk add dnscrypt-proxy2

Configure

Edit /etc/dnscrypt-proxy2/dnscrypt-proxy.toml — key changes:

# Listen on port 5353 (not 53, that's dnsmasq)
listen_addresses = ['127.0.0.1:5353']

# Quad9 DoH with DNSSEC + malware filter + ECS
server_names = ['quad9-doh-ip4-port443-filter-ecs-pri']

# Don't require nofilter — we WANT Quad9's malware filtering
require_nofilter = false

# Allow expired certs on startup (no battery-backed clock on ER605)
cert_ignore_timestamp = true

# Cache (recommended for 128MB RAM)
cache = true
cache_size = 1024
cache_min_ttl = 600
cache_max_ttl = 86400

# Block IPv6 if not used
block_ipv6 = true
Important: Make sure there's only ONE active listen_addresses line. Check with: grep -n '^listen_addresses' /etc/dnscrypt-proxy2/dnscrypt-proxy.toml

Redirect dnsmasq to dnscrypt-proxy

uci delete dhcp.@dnsmasq[0].server 2>/dev/null
uci add_list dhcp.@dnsmasq[0].server='127.0.0.1#5353'
uci set dhcp.@dnsmasq[0].noresolv='1'
uci set dhcp.@dnsmasq[0].logqueries='0'
uci commit dhcp
service dnsmasq restart
Critical: do not skip noresolv='1'. Without it, dnsmasq still uses the ISP's DNS servers received via DHCP on the WAN interface — your queries will leak to the ISP even though dnscrypt-proxy is running. Verify with: uci get dhcp.@dnsmasq[0].noresolv (must return 1).

Force the router itself to use encrypted DNS

The steps above only cover dnsmasq (which serves LAN clients). The router's own DNS resolution uses /etc/resolv.conf, which is populated by the ISP's DNS servers received via DHCP on the WAN interface. Without this step, the router itself still sends unencrypted DNS queries to your ISP.

uci set network.wan.peerdns='0'
uci set network.wan.dns='127.0.0.1'
uci commit network
service network restart
Critical: do not skip this. noresolv='1' only affects dnsmasq (LAN clients). The router's own system processes — curl (blocklist downloads), chrony (NTP resolution), apk (package updates) — use /etc/resolv.conf, which still contains your ISP's raw DNS servers unless you disable peerdns. Verify with: cat /etc/resolv.conf (should show 127.0.0.1, not your ISP's IPs).

Enable and verify

service dnscrypt-proxy start
service dnscrypt-proxy enable
netstat -tlnup | grep 5353       # Should show dnscrypt-proxy
apk add bind-dig
dig @127.0.0.1 -p 5353 example.com  # Should resolve

From a device on your network, visit dnsleaktest.com → Extended test. You should see Quad9 servers, not your ISP.

Network-wide Ad Blocking

Direct dnsmasq-based blocking using the Hagezi Pro++ blocklist (~240,000 domains). No extra packages needed — dnsmasq loads the blocklist from a config directory in /tmp (RAM) because the blocklist is too large for the router's limited flash storage (~7MB free). Since /tmp is cleared on every reboot, an init script recreates the directory and a hotplug script re-downloads the list when WAN comes up. Blocks ads, tracking, malware, and phishing for every device on the network.

Set up dnsmasq config directory

mkdir -p /tmp/dnsmasq.d
# delete first to avoid duplicates if you run this more than once
uci delete dhcp.@dnsmasq[0].confdir 2>/dev/null
uci add_list dhcp.@dnsmasq[0].confdir='/tmp/dnsmasq.d'
uci commit dhcp

Since /tmp is cleared on every reboot, create an init script to recreate the directory before dnsmasq starts (priority 18, dnsmasq is 19):

cat > /etc/init.d/mkdir-dnsmasq-confdir << 'EOF'
#!/bin/sh /etc/rc.common
START=18
start() {
    mkdir -p /tmp/dnsmasq.d
}
EOF
chmod +x /etc/init.d/mkdir-dnsmasq-confdir
/etc/init.d/mkdir-dnsmasq-confdir enable

Create the update script

cat > /usr/sbin/update-blocklist.sh << 'EOF'
#!/bin/sh
curl -s -o /tmp/dnsmasq.d/blocklist.conf \
  https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/dnsmasq/pro.plus.txt && \
/etc/init.d/dnsmasq restart
EOF
chmod +x /usr/sbin/update-blocklist.sh

Download blocklist and verify

/usr/sbin/update-blocklist.sh
wc -l /tmp/dnsmasq.d/blocklist.conf  # Should show ~240k lines
nslookup ads.google.com              # Should return NXDOMAIN
nslookup google.com                  # Should resolve normally

Switch blocklist

You can switch to a different Hagezi blocklist at any time. Edit the URL in the update script. Available options (dnsmasq format):

List Domains LIST_NAME Description
Light ~95k light Ads + tracking only. Minimal breakage
Multi ~130k multi Ads, tracking, and some malware
Pro ~180k pro Ads, tracking, malware, phishing
Pro++ ~240k pro.plus Pro + extra aggressive blocking ← used in this guide
Ultimate ~350k ultimate Maximum blocking. May break some sites

To switch, edit the script and replace pro.plus with the LIST_NAME from the table, then re-run:

sed -i 's|/dnsmasq/.*\.txt|/dnsmasq/LIST_NAME.txt|' /usr/sbin/update-blocklist.sh
/usr/sbin/update-blocklist.sh
wc -l /tmp/dnsmasq.d/blocklist.conf  # Verify the new domain count

Auto-update lists daily

echo '0 4 * * * /usr/sbin/update-blocklist.sh' >> /etc/crontabs/root
/etc/init.d/cron restart

Auto-load blocklist on boot

The init script above creates the directory, but the blocklist file itself is also lost on reboot. This hotplug script re-downloads it automatically when the WAN interface gets a public IP:

cat > /etc/hotplug.d/iface/99-blocklist << 'EOF'
#!/bin/sh
[ "$ACTION" = ifup ] && [ "$INTERFACE" = wan ] && {
  sleep 5
  /usr/sbin/update-blocklist.sh
}
EOF
chmod +x /etc/hotplug.d/iface/99-blocklist

Allowlist a domain

Remove the matching line from the blocklist and restart dnsmasq:

sed -i '/example\.com/d' /tmp/dnsmasq.d/blocklist.conf
/etc/init.d/dnsmasq restart

Note: the domain will be re-blocked on the next daily update. For a permanent allowlist, create a separate file:

# Add to a persistent allowlist that survives updates
mkdir -p /etc/dnsmasq.d
echo 'server=/example.com/#' > /etc/dnsmasq.d/allowlist.conf
uci add_list dhcp.@dnsmasq[0].confdir='/etc/dnsmasq.d'
uci commit dhcp
/etc/init.d/dnsmasq restart

NTP + NTS (Chrony)

Secure time synchronization with Cloudflare via Network Time Security (NTS). Prevents time-based attacks and serves NTP to all LAN devices.

Install

apk update
apk del chrony
apk add chrony-nts ca-certificates
rm -f /etc/config/chrony-opkg

Configure Cloudflare NTS

# Remove default pool and server entries (pool overrides NTS)
while uci -q get chrony.@pool[0]; do uci delete chrony.@pool[0]; done
while uci -q get chrony.@server[0]; do uci delete chrony.@server[0]; done

# Add Cloudflare NTS server
uci add chrony server
uci set chrony.@server[-1].hostname='time.cloudflare.com'
uci set chrony.@server[-1].iburst='yes'
uci set chrony.@server[-1].nts='yes'
uci commit chrony
/etc/init.d/chronyd restart

Serve NTP to your LAN

By default chrony only syncs its own clock. To make the router act as a local NTP server for all LAN devices, you need to allow your subnet and disable the built-in sysntpd so it doesn't conflict.

# Disable the built-in NTP server (busybox sysntpd) — chrony replaces it
/etc/init.d/sysntpd stop
/etc/init.d/sysntpd disable

# Enable NTP server on port 123 and allow LAN clients
# We use conf.d because chrony defaults to port 0 (NTP disabled)
# and the UCI 'allow' option ignores the 'subnet' field.
mkdir -p /etc/chrony/conf.d
cat > /etc/chrony/conf.d/ntp-server.conf << 'EOF'
port 123
allow 192.168.1.0/24
EOF
/etc/init.d/chronyd restart
How it works: The router fetches time from Cloudflare using NTS (authenticated + encrypted). It then serves plain NTP to LAN clients. This is fine — NTS protects the internet leg (router ↔ Cloudflare), while the LAN segment (client ↔ router) is already trusted.

Verify NTP server + NTS

# Check NTS sync with Cloudflare
chronyc sources -a     # Should show time.cloudflare.com with ^*
chronyc -N authdata    # Mode should be NTS, Cook > 0
chronyc tracking       # Leap status should be Normal

# Check the router is serving NTP to the LAN
chronyc serverstats    # NTP packets received > 0 means clients are connecting
netstat -ulnp | grep 123  # Should show chronyd on 0.0.0.0:123 (all interfaces)
If "NTP packets received" stays at 0: verify that at least one LAN device is configured to use 192.168.1.1 as its NTP server. Also check that netstat -ulnp | grep 123 shows 0.0.0.0:123 (all interfaces) — if it shows 127.0.0.1:123, chrony is only serving localhost.
Linux (chrony) — point chrony to router

Many Linux distros (Fedora, RHEL, Arch, etc.) ship with chrony by default. Replace the default NTP pool/server entries with your router:

# Find your chrony config (varies by distro)
# Fedora/RHEL/Arch: /etc/chrony.conf
# Debian/Ubuntu:    /etc/chrony/chrony.conf
CONF=/etc/chrony.conf
[ -f /etc/chrony/chrony.conf ] && CONF=/etc/chrony/chrony.conf

# Comment out existing pool/server lines (skip if already correct)
sudo sed -i '/^server 192\.168\.1\.1 iburst$/!{ s/^pool .*/#&/; s/^server .*/#&/; }' "$CONF"

# Add the router line only if not already present (idempotent)
grep -qx 'server 192.168.1.1 iburst' "$CONF" || \
  echo -e '\nserver 192.168.1.1 iburst' | sudo tee -a "$CONF"

# Restart chrony and verify
sudo systemctl restart chronyd
chronyc sources -a     # Should show 192.168.1.1 with ^*
chronyc tracking       # Ref ID should point to 192.168.1.1
Tip: You don't need NTS for the LAN leg (client ↔ router). NTS secures the internet leg (router ↔ Cloudflare). Your router already verified time authenticity, so plain NTP over your local network is safe.
Important: chronyc -h 192.168.1.1 tracking will fail with "506 Cannot talk to daemon" — this is normal. That command uses chrony's admin protocol (port 323), which is restricted to localhost for security. NTP clients use port 123, which is what matters. Use chronyc sources -a on the client to verify sync.
Linux (systemd-timesyncd) — point timesyncd to router

Some distros (Ubuntu, Debian, Linux Mint) use systemd-timesyncd instead of chrony. Check which one you have and configure accordingly:

# Check which NTP client is active
timedatectl timesync-status 2>/dev/null && echo "Using systemd-timesyncd" || echo "Not using timesyncd"

# Configure timesyncd to use the router
sudo mkdir -p /etc/systemd/timesyncd.conf.d
printf '[Time]\nNTP=192.168.1.1\nFallbackNTP=\n' | sudo tee /etc/systemd/timesyncd.conf.d/router.conf

# Restart and verify
sudo systemctl restart systemd-timesyncd
timedatectl timesync-status   # Server should show 192.168.1.1

Firewall Hardening

OpenWrt's default config is already solid. These two tweaks improve security and performance with no risk.

Drop invalid packets

Network → Firewall → General Settings → check "Drop invalid packets". Drops malformed or out-of-sequence packets that are often part of port scans or fingerprinting attacks.

Software flow offloading

Network → Firewall → Routing/NAT Offloading → "Software flow offloading". Established connections bypass nftables rules for much better throughput on the MT7621.

If you use SQM (QoS): do NOT enable hardware flow offloading — it bypasses traffic shaping. Software offloading only is fine with SQM.

Performance

Tweaks to improve throughput, DNS response times, and reduce resource usage on the ER605's dual-core MT7621 with 128MB of RAM. All of these are reversible.

Enable Packet Steering

By default, all network interrupts land on a single CPU core. Packet Steering distributes packet processing across both cores of the MT7621, reducing bottlenecks under heavy traffic.

uci set network.globals.packet_steering='1'
uci commit network
service network restart
Also available in LuCI: Network → Interfaces → Global network options → Packet Steering.

Enable Software Flow Offloading

Allows established connections to bypass the full firewall/NAT processing path, significantly improving throughput. Already recommended in the Post-Install section via LuCI — here's the UCI equivalent.

uci set firewall.@defaults[0].flow_offloading='1'
uci commit firewall
service firewall restart
Note: Incompatible with SQM/QoS. Traffic statistics in LuCI may be less accurate since offloaded packets bypass the normal counters.

Increase dnsmasq DNS cache

The default cache holds only 150 entries. Increasing it to 1000 means repeated DNS queries resolve instantly from local cache instead of hitting dnscrypt-proxy again. The memory cost is negligible (~100KB).

uci set dhcp.@dnsmasq[0].cachesize='1000'
uci commit dhcp
service dnsmasq restart

Disable IPv6 if your ISP doesn't provide it

odhcpd handles DHCPv6 and Router Advertisements for IPv6. If your ISP doesn't give you IPv6 (most residential connections in Latin America don't), it's running for nothing — using RAM and CPU cycles with no benefit.

First, check if you actually have IPv6:

ip -6 addr show scope global

If it returns nothing (no global IPv6 address), you can safely disable odhcpd:

service odhcpd stop
service odhcpd disable
To re-enable later: service odhcpd enable && service odhcpd start. This is completely reversible.

Reduce system log buffer

By default, logd keeps a 64KB log buffer in RAM. Reducing it to 32KB saves memory while still keeping enough log history for basic debugging:

uci set system.@system[0].log_size='32'
uci commit system
/etc/init.d/log restart

Check current RAM usage

Monitor available memory at any time:

free -m

With everything running (dnsmasq + blocklist + dnscrypt-proxy + chrony), expect around 50–60MB free. That's plenty for the ER605.

Quick Reference

Command What it does
ssh [email protected]Connect to router
service <name> start/stop/restartControl a service
service <name> enable/disableEnable/disable on boot
logread -e <service>View logs for a service
netstat -tlnupShow listening ports
free -mRAM usage
df -hDisk / overlay space
apk list --installedInstalled packages
uci show <component>View UCI config
Active service Function
update-blocklist.shDNS blocklist via dnsmasq (Hagezi)
chronydTime sync (NTP + NTS)
dnscrypt-proxyEncrypted DNS (DNSCrypt/DoH)
dnsmasqLocal DNS + DHCP
dropbearSSH server
firewallnftables (fw4)

Post-Reboot Verification

All configuration set via uci commit persists across reboots (stored in /etc/config/ on flash). Services enabled with service <name> enable start automatically on boot. Run these commands after a reboot to confirm everything is working:

# dnsmasq → dnscrypt-proxy forwarding
uci get dhcp.@dnsmasq[0].server       # → 127.0.0.1#5353
uci get dhcp.@dnsmasq[0].noresolv     # → 1
uci get dhcp.@dnsmasq[0].logqueries   # → 0

# Router's own DNS (not leaking to ISP)
uci get network.wan.peerdns            # → 0
uci get network.wan.dns                # → 127.0.0.1
cat /etc/resolv.conf                   # → search lan + nameserver 127.0.0.1 (no ISP IPs)

# dnscrypt-proxy listening
netstat -tlnup | grep 5353            # → dnscrypt-proxy on TCP+UDP

# Ad blocking active (blocklist loaded)
wc -l /tmp/dnsmasq.d/blocklist.conf   # → ~240k lines

# NTP with NTS + LAN server
chronyc sources -a                     # → time.cloudflare.com with ^*
chronyc -N authdata                    # → NTS mode, cookies > 0
chronyc serverstats                    # → NTP packets received > 0

# DNS resolves correctly
dig @127.0.0.1 -p 5353 example.com    # → NOERROR + IP address
nslookup ads.google.com               # → NXDOMAIN (blocked)

# Performance tweaks
uci get network.globals.packet_steering   # → 1
uci get firewall.@defaults[0].flow_offloading  # → 1
uci get dhcp.@dnsmasq[0].cachesize       # → 1000
Check Expected result
uci get dhcp.@dnsmasq[0].server 127.0.0.1#5353
uci get dhcp.@dnsmasq[0].noresolv 1
uci get dhcp.@dnsmasq[0].logqueries 0
uci get network.wan.peerdns 0
uci get network.wan.dns 127.0.0.1
cat /etc/resolv.conf 127.0.0.1 only (no ISP IPs)
netstat -tlnup | grep 5353 dnscrypt-proxy on TCP + UDP
wc -l /tmp/dnsmasq.d/blocklist.conf ~240k lines (blocklist loaded)
chronyc sources -a time.cloudflare.com with ^*
chronyc -N authdata NTS mode, cookies > 0
chronyc serverstats NTP packets received > 0 (LAN server)
nslookup ads.google.com NXDOMAIN
uci get network.globals.packet_steering 1
uci get firewall.@defaults[0].flow_offloading 1
uci get dhcp.@dnsmasq[0].cachesize 1000

Troubleshooting

SSH: Connection refused
Verify Remote Assistance is enabled. Check your PC has an IP in the 192.168.0.x range. Try restarting the router and waiting 1 minute.
SSH: "REMOTE HOST IDENTIFICATION HAS CHANGED"

Expected after flashing — the SSH server changed. Remove the old key:

ssh-keygen -R 192.168.0.1
ssh-keygen -R 192.168.1.1
SSH: "no matching host key / cipher / kex"

Use the full command with legacy algorithm flags:

ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa -c aes128-ctr [email protected]
Can't access 192.168.1.1 after reboot
This usually means dnsmasq crashed and DHCP is not running. Set a static IP to get in: sudo nmcli con mod "Wired connection 1" ipv4.method manual ipv4.addresses 192.168.1.50/24 ipv4.gateway 192.168.1.1, then reconnect. SSH in and check: logread | grep dnsmasq. Common causes: (1) /tmp/dnsmasq.d doesn't exist — verify the init script is enabled: /etc/init.d/mkdir-dnsmasq-confdir enable. (2) Duplicate confdir entries — fix with: uci delete dhcp.@dnsmasq[0].confdir; uci add_list dhcp.@dnsmasq[0].confdir='/tmp/dnsmasq.d'; uci commit dhcp. Remember to switch your PC back to DHCP after.
dnscrypt-proxy: port 53 already in use
There are multiple active listen_addresses lines in the .toml, or the active one points to port 53 (used by dnsmasq). Ensure only one line exists pointing to 127.0.0.1:5353.
Blocklist is empty or not loaded
Re-run the update script: /usr/sbin/update-blocklist.sh. Check that curl can reach the CDN: curl -sI https://cdn.jsdelivr.net. Verify the file exists: ls -la /tmp/dnsmasq.d/blocklist.conf.
DNS doesn't resolve anything
Remove the blocklist temporarily: rm /tmp/dnsmasq.d/blocklist.conf && /etc/init.d/dnsmasq restart. Check dnsmasq: service dnsmasq status. Test dnscrypt-proxy directly: dig @127.0.0.1 -p 5353 example.com. If that works but the network doesn't resolve, restart dnsmasq.
Credentials don't work after downgrade/rollback
Do a hardware factory reset: hold the RESET button with a pin for 10 seconds. The router will ask you to create new credentials. Do not guess — you'll get locked out.

Revert to Stock Firmware

The restore command depends on how your backup was made. Using the wrong command (ubiformat vs mtd write) can destroy the NAND bad block table and permanently brick the router. Consult the MTD backup documentation before restoring.

Rollback DNS changes

uci delete dhcp.@dnsmasq[0].server
uci set dhcp.@dnsmasq[0].noresolv='0'
uci delete dhcp.@dnsmasq[0].logqueries 2>/dev/null
uci commit dhcp
uci set network.wan.peerdns='1'
uci delete network.wan.dns 2>/dev/null
uci commit network
service dnsmasq restart
service network restart

Rollback NTP/NTS changes

apk del chrony-nts
apk add chrony
rm -f /etc/chrony/conf.d/ntp-server.conf
uci delete chrony.@server[0]
uci add chrony pool
uci set chrony.@pool[-1].hostname='2.openwrt.pool.ntp.org'
uci set chrony.@pool[-1].maxpoll='12'
uci set chrony.@pool[-1].iburst='yes'
uci commit chrony
/etc/init.d/chronyd restart
/etc/init.d/sysntpd enable
/etc/init.d/sysntpd start

Usage Guide

Common day-to-day tasks for managing your OpenWrt-powered ER605 v2 via the command line (SSH).

Assign a Static IP (DHCP Static Lease)

Each uci add dhcp host creates a new "host" entry inside the DHCP configuration. Each host entry is a static lease — it binds a device's MAC address to a fixed IP so it always gets the same address.

Add a single static lease

# Replace the values with your device's info
uci add dhcp host
uci set dhcp.@host[-1].name='MyDevice'
uci set dhcp.@host[-1].mac='AA:BB:CC:DD:EE:FF'
uci set dhcp.@host[-1].ip='192.168.1.100'
uci commit dhcp
/etc/init.d/dnsmasq restart
After assigning a static IP, restart or reconnect the target device so it picks up the new address from DHCP.

Example: multiple devices

# Access Point 1
uci add dhcp host
uci set dhcp.@host[-1].name='AP-Living-Room'
uci set dhcp.@host[-1].mac='2C:3B:70:AA:11:22'
uci set dhcp.@host[-1].ip='192.168.1.2'

# Access Point 2
uci add dhcp host
uci set dhcp.@host[-1].name='AP-Office'
uci set dhcp.@host[-1].mac='2C:3B:70:BB:33:44'
uci set dhcp.@host[-1].ip='192.168.1.3'

# NAS
uci add dhcp host
uci set dhcp.@host[-1].name='NAS'
uci set dhcp.@host[-1].mac='00:11:32:CC:55:66'
uci set dhcp.@host[-1].ip='192.168.1.10'

# Apply all at once
uci commit dhcp
/etc/init.d/dnsmasq restart

List all static leases

uci show dhcp | grep host

Remove a static lease

# First find the index of the host you want to remove
uci show dhcp | grep host

# Then delete it (replace [0] with the correct index)
uci delete dhcp.@host[0]
uci commit dhcp
/etc/init.d/dnsmasq restart

View Connected Devices

Active DHCP leases

cat /tmp/dhcp.leases

ARP table (all devices seen on the network)

ip neigh

Port Forwarding

# Example: forward external port 8080 to internal 192.168.1.100:80
uci add firewall redirect
uci set firewall.@redirect[-1].name='Web-Server'
uci set firewall.@redirect[-1].src='wan'
uci set firewall.@redirect[-1].src_dport='8080'
uci set firewall.@redirect[-1].dest='lan'
uci set firewall.@redirect[-1].dest_ip='192.168.1.100'
uci set firewall.@redirect[-1].dest_port='80'
uci set firewall.@redirect[-1].proto='tcp'
uci set firewall.@redirect[-1].target='DNAT'
uci commit firewall
/etc/init.d/firewall restart

Change LAN IP / Subnet

uci set network.lan.ipaddr='192.168.10.1'
uci set network.lan.netmask='255.255.255.0'
uci commit network
/etc/init.d/network restart
Changing the LAN IP will disconnect your SSH session. Reconnect using the new IP address.

System Info & Reboot

# Check uptime
uptime

# Check OpenWrt version
cat /etc/openwrt_release

# Reboot the router
reboot

Useful UCI Tips

Command Description
uci show <config> Show all settings for a config (dhcp, network, firewall…)
uci changes Show uncommitted changes
uci commit Apply all pending changes
uci revert <config> Discard uncommitted changes for a config
logread -f Follow the system log in real time
df -h Check disk/flash storage usage

Sysupgrade Guide

How to upgrade OpenWrt to a newer release on your already-flashed ER605 v2. This example covers upgrading to 25.12.2 (latest stable as of April 2026).

Post-upgrade system info

Hostname
OpenWrt
Model
TP-Link ER605 v2
Architecture
MediaTek MT7621 ver:1 eco:3
Target Platform
ramips/mt7621
Firmware Version
OpenWrt 25.12.2 r32802-f505120278
Kernel Version
6.12.74
1

Download the sysupgrade image

Download the sysupgrade binary from the official OpenWrt downloads.

cd /tmp
curl -o openwrt-25.12.2-sysupgrade.bin \
  https://downloads.openwrt.org/releases/25.12.2/targets/ramips/mt7621/openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin
File openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin
Size 6.32 MiB
MD5 c67df3d3afd4358edc566dbcc5d66dc7
SHA256 20e99c590c5824033f8d34c6acff3738aca6ec42bf159c16530ae1680a8f08c0
2

Verify the checksum

Always verify the SHA256 hash before flashing to ensure file integrity.

sha256sum openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin

Expected: 20e99c59...a8f08c0

3

Backup your current configuration

Using -n (or unchecking "Keep settings" in LuCI) wipes all settings. If you have a custom config (firewall rules, DNS, static leases, etc.), download a backup first.

In LuCI: System → Backup / Flash Firmware → Generate archive

4

Flash via LuCI (web interface)

  1. Go to System → Backup / Flash Firmware → Flash new firmware image
  2. Select openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin and click Upload
  3. Verify the checksums shown match the values above (MD5 & SHA256)
  4. Uncheck "Keep settings and retain the current configuration"
  5. Click Continue and wait several minutes — do NOT power off the router
If you see "Image check failed" in red, make sure "Keep settings" is unchecked. Do NOT use "Force upgrade".
alt

Alternative: flash via SSH

If you prefer the command line, transfer the file to the router and run sysupgrade:

# From your PC — transfer the image to the router:
scp openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin [email protected]:/tmp/

# On the router via SSH:
cd /tmp
sysupgrade -n -v openwrt-25.12.2-ramips-mt7621-tplink_er605-v2-squashfs-sysupgrade.bin

The -n flag means "do not keep settings" (clean install). -v enables verbose output.

5

Post-upgrade: reconfigure

After reboot, the router is at 192.168.1.1 with default settings (root, no password). You need to reconfigure everything: root password, firewall, DNS, DHCP, etc.

6

Disable automatic firmware upgrade check

By default, LuCI checks for firmware upgrades on login. You can disable this to avoid unnecessary external requests:

uci set attendedsysupgrade.client.login_check_for_upgrades='0'
uci commit attendedsysupgrade

This prevents the attended sysupgrade client from calling home every time you open LuCI.

Links & Resources