How to Remove Ads from Apps Using VPN and Unbound DNS

Ad-Free Apps: Using VPN and Unbound to Block Banners in Applications

Recently, I bought a Xiaomi phone and was happy with it—except for the ads in the stock apps. Yes, there’s a setting to disable them, but it doesn’t get rid of all the ads. That’s why I rarely use my phone for browsing, since you can’t install ad blockers in most mobile browsers.

Some people say ads help support free projects, and maybe that was true once. But now, ads are mostly about collecting data on you—your habits, search queries, and more. Just look at the traffic your phone generates during normal use. On my desktop, I’ve long used plugins like NoScript and uBlock, so I almost forgot about ads—until my phone kept reminding me.

The last straw was when I analyzed the traffic from the Xiaomi MiFit app and found links to suspicious APK files in the server responses (I won’t describe them here, but you can intercept these requests yourself—they use plain HTTP).

I had a spare server with a public IP, so I decided to set up a VPN on it and configure ad blocking for all connected devices—once and for all.

Preparation

You’ll need a server with a public IP running Linux (I used Ubuntu 16.10, but these steps work on other distros—just adjust the package manager commands as needed).

What to Do with an Old Ubuntu Version

If your server has outdated repository links, you’ll get errors when updating. The repositories have moved to the old-releases subdomain. Fix and update with:

sed -i 's|us.archive|old-releases|' /etc/apt/sources.list
sed -i 's|//security|//old-releases|' /etc/apt/sources.list
apt update && apt upgrade -y && reboot

Installing OpenVPN

There are two ways:

  • Easy: Use a ready-made Docker image (for those who don’t want to mess with configs).
  • Advanced: Install and configure OpenVPN manually.

We’ll cover both, starting with the easy way.

OpenVPN Docker Image

  1. Download the image:
    docker pull kylemanna/openvpn
  2. Create the VPN server config:
    docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_genconfig -u udp://YOUR_SERVER_IP

    Replace YOUR_SERVER_IP with your server’s public IP.

  3. Generate certificates:
    docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_initpki
  4. Add a user (replace CLIENTNAME with your client’s hostname):
    docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full CLIENTNAME nopass
  5. Get the client config:
    docker run -v /etc/openvpn:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn ovpn_getclient CLIENTNAME > /root/CLIENTNAME.ovpn
  6. Start OpenVPN as a daemon:
    docker run -v /etc/openvpn:/etc/openvpn -d -p 1194:1194/udp --cap-add=NET_ADMIN kylemanna/openvpn
  7. On the client, connect with:
    sudo openvpn --config CLIENTNAME.ovpn

If you see Initialization Sequence Completed in the log and a tun0 interface appears, you’re good to go. Add the container to autostart and move on to DNS setup.

Manual OpenVPN Setup

  1. Install OpenVPN:
    sudo apt install -y openvpn
  2. Clone easy-rsa:
    sudo git clone https://github.com/OpenVPN/easy-rsa.git /etc/openvpn/easy-rsa
  3. Edit /etc/openvpn/easy-rsa/easyrsa3/vars to set variables like:
    set_var EASYRSA_KEY_SIZE    2048
    set_var EASYRSA_CA_EXPIRE   3650
    set_var EASYRSA_CERT_EXPIRE 3650
    
  4. Initialize PKI:
    cd /etc/openvpn/easy-rsa/easyrsa3 && ./easyrsa init-pki
  5. Build CA:
    ./easyrsa build-ca
  6. Build server keys:
    ./easyrsa build-server-full vpnserver nopass
  7. Build client keys:
    ./easyrsa build-client-full user-pc nopass
  8. Optionally, generate Diffie-Hellman and TLS keys:
    ./easyrsa gen-dh
    openvpn --genkey --secret /etc/openvpn/easy-rsa/easyrsa3/pki/ta.key
    

Write your server config to /etc/openvpn/server.conf (see the original article for a sample config). Adjust the systemd service if needed, then enable and start OpenVPN:

sudo systemctl enable openvpn.service && sudo systemctl start openvpn.service
journalctl -uxe openvpn.service

To generate client configs, you can use a simple bash script (see the original article for details).

Setting Up Unbound DNS Resolver

I chose Unbound because it supports DNSSEC, caching, DNS over TLS (DoT), is easy to configure, and is faster and lighter than BIND.

Compiling Unbound

  1. Clone Unbound and install dependencies:
    git clone https://github.com/NLnetLabs/unbound.git /opt/unbound
    sudo apt install -y protobuf-c-compiler libevent-dev libssl-dev libsodium-dev libfstrm-dev
    
  2. Compile and install:
    cd /opt/unbound
    ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --sbindir=/usr/bin --disable-rpath --enable-dnscrypt --enable-dnstap --enable-pie --enable-relro-now --enable-subnet --enable-tfo-client --enable-tfo-server --with-conf-file=/etc/unbound/unbound.conf --with-pidfile=/run/unbound.pid --with-rootkey-file=/etc/trusted-key.key --with-libevent
    make
    sudo make install
    
  3. Create the unbound user:
    sudo useradd -s /bin/false -d /etc/unbound unbound
  4. Add the systemd service at /lib/systemd/system/unbound.service (see the original article for content).
  5. Add a tmpfiles.d anchor:
    echo 'C /etc/unbound/trusted-key.key - - - - /etc/trusted-key.key' > /usr/lib/tmpfiles.d/unbound.conf

Configuring DNS over TLS

DoT adds privacy and protects against spoofing. Here’s a sample /etc/unbound/unbound.conf (see the original article for the full config):

  • Listen on all interfaces
  • Log to syslog
  • Allow queries only from localhost and VPN subnets
  • Forward all DNS queries to DoT-enabled servers (e.g., Cloudflare)

Generate DNSSEC trust anchors:

sudo unbound-anchor -a /etc/unbound/trusted-key.key

Integrating with NetworkManager

To update /etc/resolv.conf, install openresolv and create /etc/resolvconf.conf:

resolv_conf=/etc/resolv.conf
name_servers="::1 127.0.0.1"

Update resolv.conf with:

resolvconf -u

If you use NetworkManager, set rc-manager=resolvconf in /etc/NetworkManager/conf.d/rc-manager.conf.

Start Unbound:

sudo systemctl start unbound

Warning: Make sure no other DNS servers or resolvers are running before starting Unbound. Check errors with journalctl -xeu unbound.

Ad Blocking

Now for the most important part. I found several domain lists for ad blocking, such as:

Create a script at /etc/unbound/local.d/update_ads_filters.py to parse these lists and generate Unbound rules:

#!/usr/bin/python
import requests

response = requests.get('https://pgl.yoyo.org/adservers/serverlist.php?hostformat=unbound&showintro=0&mimetype=plaintext')
hosts = requests.get('http://www.malwaredomainlist.com/hostslist/hosts.txt')

def make_unbound_zone(host):
    fmt = 'local-zone: "{0}" redirect\\nlocal-data: "{0} A 127.0.0.1"\\n'
    return fmt.format(host)

if response and response.status_code == 200:
    unbound_format = response.text
    if hosts and hosts.status_code == 200:
        for line in hosts.text.splitlines():
            if not line or line.startswith('#'):
                continue
            _, host = line.strip().split()
            if host not in unbound_format:
                unbound_format += make_unbound_zone(host)

print(unbound_format)

This creates entries like:

local-zone: "HOSTNAME" redirect
local-data: "HOSTNAME A 127.0.0.1"

Alternatively, you can use always_nxdomain to return NXDOMAIN:

local-zone: "HOSTNAME" always_nxdomain

Add this script to crontab to run weekly:

/etc/unbound/local.d/update_ads_filters.py > /etc/unbound/local.d/adservers.conf && unbound-control reload

For manual blocking, create /etc/unbound/insert_filter_domain.sh:

#!/bin/bash
DOMAIN=$1
FILTER_FILE="/etc/unbound/local.d/ruads.conf"
echo "local-zone: \\"${DOMAIN}\\" always_nxdomain" >> ${FILTER_FILE}
unbound-control reload

DNS Resolution for Clients

It’s nice to use hostnames instead of IPs, especially since OpenVPN’s built-in DHCP assigns dynamic IPs. To automate DNS records for clients, update your OpenVPN config (/etc/openvpn/server.conf):

script-security 2
client-connect /etc/openvpn/connect.sh
client-disconnect /etc/openvpn/connect.sh

Then create /etc/openvpn/connect.sh:

#!/bin/bash

add_to_unbound() {
    host="$1"
    ip="$2"
    reverse_ip=$(echo $ip | awk ' BEGIN { FS="." ; OFS="." } { print $4,$3,$2,$1 }')
    unbound-control local_data "${host} IN A $ip"
    unbound-control local_data "${reverse_ip}.in-addr.arpa. IN PTR ${host}"
}

del_from_unbound() {
    host="$1"
    ip="$2"
    reverse_ip=$(echo $ip | awk ' BEGIN { FS="." ; OFS="." } { print $4,$3,$2,$1 }')
    unbound-control local_data_remove "${host}"
    unbound-control local_data_remove "${reverse_ip}.in-addr.arpa."
}

if [ "$script_type" == "client-connect" ]; then
    add_to_unbound "${common_name}" "${ifconfig_pool_remote_ip}"
elif [ "$script_type" == "client-disconnect" ]; then
    del_from_unbound "${common_name}" "${ifconfig_pool_remote_ip}"
fi

This script uses OpenVPN’s environment variables to add or remove DNS records for each client as they connect or disconnect.

Also, create /etc/unbound/local.d/localzone.conf for server and subnet records:

insecure-lan-zones: yes

local-zone: "10.in-addr.arpa." nodefault 
local-zone: "8.10.in-addr.arpa." nodefault

local-data-ptr: "10.8.0.1 vpnserver"
local-data: "vpnserver A 10.8.0.1"

Conclusion

That’s it! Install OpenVPN on your phone, connect, and test. You should see a dramatic reduction in ads, both in browsers and apps like MiFit and Avito. Enjoy your ad-free experience!

Leave a Reply