Skip to content

Running Clash on OpenWrt as a transparent proxy

I will revamp this post soon as Clash is going to have major changes.

As you would have been aware of that I live in China where internet is under strict censorship. I’ve been discovering ways to access the blocked internet resources.

So recently I switched to a x86 mini computer that runs Proxmox VE, which has an OpenWRT VM running as a router. In this blog post, I’m using Clash, a new software that is quite the same to Surge. They both support “rules” mode that routes internet traffic on your will. It’s so convenient that you don’t have to use gfwlist anymore, and they are more precise and customizable, like you can route Google to a Hong Kong proxy, YouTube to United States, Netflix to Japan and so forth. Clash also has a redir mode which can be used with iptables to redirect the TCP packets. We’re also gonna utilize Unbound and DNSCrypt-proxy to solve the DNS pollution issue.

Download the tools #

$ mkdir /etc/clash
$ cd /etc/clash
$ wget
$ tar -xzvf clash-linux-amd64.tar.gz
$ mv clash-linux-amd64 clash
$ mkdir /etc/unbound
$ opkg update && opkg install unbound luci-app-unbound

Follow this guide to install DNSCrypt-proxy:

Make them into services #

Put the following script to /etc/init.d/clash:

#!/bin/sh /etc/rc.common



start_service() {
        procd_set_param command /etc/clash/clash -d /etc/clash
        procd_set_param respawn 300 0 5 # threshold, timeout, retry
        procd_set_param file /etc/clash/config.yml
        procd_set_param stdout 1
        procd_set_param stderr 1
        procd_set_param pidfile /var/run/

Then run the following to make it executable:

$ chmod +x /etc/init.d/clash

This init.d script also immediately restarts clash when it exits for whatever reason. If it crashes 5 times in 5 minutes, it won’t be restarted anymore.

The logs are in /var/log/messages.

Write Clash Configuration #

I’m not covering how to write Clash configuration in this blog post, but these options must be set as follows:

redir-port: 9090
allow-lan: true
  enable: true
  ipv6: false
  enhanced-mode: redir-host

Let’s tear it down. 9090 is the redir port, allow-lan allows other devices in LAN to access the proxy and external-controller is the API that we’re gonna use later to control Clash.

Clash will now forward DNS requests from :53 to unbound (:5353), which forwards DNS requests to DNSCrypt-proxy (:5678). DNSCrypt-proxy will then securely get the correct DNS responses using DoH.

Configure dnsmasq #

# Disable dnsmasq DNS server
$ uci set 'dhcp.@dnsmasq[0].port=0'

# Configure dnsmasq to send a DNS Server DHCP option with its LAN IP
# since it does not do this by default when port is configured.
$ lan_address=$(uci get network.lan.ipaddr)
$ uci add_list "dhcp.lan.dhcp_option=option:dns-server,$lan_address"

$ uci commit
$ service dnsmasq restart

Configure DNSCrypt-proxy #

Use the example config with these options changed:

server_names = ['cloudflare', 'google']
listen_addresses = ['']
fallback_resolver = ''
ignore_system_dns = true
forwarding_rules = 'forwarding-rules.txt'

Configure Unbound #

First download named.cache from InterNIC:

$ wget ftp://FTP.INTERNIC.NET/domain/named.cache -O/etc/unbound/root.hints

Then enable manual config so we can configure Unbound directly using it’s config file:

$ uci set 'unbound.@unbound[0].manual_conf=1'

To make China domains solve through instead of foreign DNSCrypt-proxy, we’re using

$ cd
$ git clone
$ cd dnsmasq-china-list
$ make SERVER= unbound
$ mkdir /etc/unbound/unbound.conf.d
$ cp accelerated-domains.china.unbound.conf /etc/unbound/unbound.conf.d

Finally, this is the config I’m currently using:

include: "/etc/unbound/unbound.conf.d/accelerated-domains.china.unbound.conf"

	verbosity: 1
	directory: "/etc/unbound"
	num-threads: 2
	msg-cache-slabs: 1
	rrset-cache-slabs: 1
	infra-cache-slabs: 1
	key-cache-slabs: 1
	access-control: allow
	outgoing-num-tcp: 256
	incoming-num-tcp: 1024
	outgoing-port-permit: "10240-65335"
	outgoing-range: 60
	num-queries-per-thread: 30
	msg-buffer-size: 8192
	infra-cache-numhosts: 200
	key-cache-size: 100k
	neg-cache-size: 10k
	target-fetch-policy: "2 1 0 0 0 0"
	harden-large-queries: yes
	harden-short-bufsize: yes
	port: 5353
	so-rcvbuf: 4m
	so-sndbuf: 4m
	so-reuseport: yes
	msg-cache-size: 64m
	rrset-cache-size: 128m
	cache-max-ttl: 3600
	do-ip4: yes
	do-ip6: yes
	do-udp: yes
	do-tcp: yes
	tcp-upstream: no
	use-syslog: yes
	log-queries: no
	root-hints: "/etc/unbound/root.hints"
	hide-identity: yes
	hide-version: yes
	identity: ""
	version: ""
	harden-glue: yes
	private-address: fd00::/8
	private-address: fe80::/10
	private-address: ::ffff:0:0/96
	unwanted-reply-threshold: 10000000
	do-not-query-localhost: no
	prefetch: yes
	minimal-responses: no
	module-config: "iterator"
    name: "."

Launch the services #

Run the following to make Clash, DNScrypt-proxy and Unbound launch at system startup:

$ service clash enable
$ service dnscrypt-proxy enable
$ service unbound enable

Apply the changes:

$ service dnscrypt-proxy restart
$ service unbound restart
$ service clash restart

Redirect the traffic to Clash #

Go to https://ROUTER_IP/cgi-bin/luci/admin/network/firewall/custom, append the following to the end of rules. Be aware that you need to change YOUR_SSH_PORT.

iptables -t nat -N clash_lan
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN
iptables -t nat -A clash_lan -d -j RETURN

# Disable the proxy for
# iptables -t nat -A clash_lan -s -j RETURN

iptables -t nat -A clash_lan -p tcp --dport YOUR_SSH_PORT -j ACCEPT
iptables -t nat -A clash_lan -p tcp --dport 80 -j REDIRECT --to-ports 9090
iptables -t nat -A clash_lan -p tcp --dport 443 -j REDIRECT --to-ports 9090
iptables -t nat -A clash_lan -p tcp --dport 53 -j REDIRECT --to-ports 53

iptables -t nat -A PREROUTING -p tcp -j clash_lan

# Chromecast
iptables -t nat -A PREROUTING -s IP_CIDR_OF_CHROMECAST_IF_YOU_HAVE_ANY -p udp --dport 53 -j REDIRECT --to-ports 53
iptables -t nat -A PREROUTING -s IP_CIDR_OF_CHROMECAST_IF_YOU_HAVE_ANY -p tcp --dport 53 -j REDIRECT --to-ports 53

You can now open your browser now and go to to see if it works!

Control Clash #

Remember external-controller? We’re gonna make use of it… right now.

There’s a fantastic web interface that does exactly the work: Use your OpenWrt IP address, and port 6170.

Be ware that Clash does not remember your choices of servers between restarts.

Check the logs #

$ logread -e clash -f

Last words #

I’m also using WireGuard to connect back to my home network when I’m not in house. If you want to know further more how to configure WireGuard to work with this approach (Clash + Unbound), comment down below.

Resources #