Skip to content

Commit fdba9de

Browse files
committed
New blog post for AdGuardHome and keepalived
1 parent 68e6a9b commit fdba9de

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
83.7 KB
Loading
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
author: Centurio
3+
title: "Run AdGuardHome With Keepalived"
4+
date: 2025-10-05T07:26:30+02:00
5+
categories:
6+
- Linux
7+
- Raspberry Pi
8+
tags:
9+
- AdGuardHome
10+
- keepalived
11+
---
12+
# Introduction
13+
I've used to run Pi-Hole at home, changed then to AdGuardHome, then to AdGuardHome DNS and today I'm back again at at combination of AdGuardHome and AdGuardHome DNS as my primary DNS services at home. The reason for this is I finally found a solution to keep two AdGuardHome installations in sync and also keeping them highly available. Today I want to document how I did that.
14+
15+
16+
# Installation
17+
I'm using two Raspberry Pis for this setup. A Pi 5 8GB and a Pi 4 2GB. The Pi 5 serves also as a docker host and therefore gets the job to sync between the AdGuardHome installations using AdGuardHome-sync. Here's a schema of the current setup:
18+
19+
![AdGuardHome setup with keepalived and sync](AdGuardHome_keepalived.png)
20+
21+
I'll leave the installation and setup of AdGuardHome and refer to [the official documentation](https://github.com/AdguardTeam/AdGuardHome?tab=readme-ov-file#automated-install-linux-and-mac) for this part. [AdGuardHome-sync](https://github.com/bakito/adguardhome-sync) is running as a docker container, using the two IPs of the AdGuardHome UIs (192.168.100.18 and 192.168.100.19).
22+
I'm running Raspbian on my Raspberry Pis and had to install for keeaplived only two packages, using `sudo apt-get install keepalived libipset13`. For the healthcheck I'm using netcat, which I've installed using `sudo apt-get install netcat-openbsd`.
23+
24+
# Configuration
25+
The configuration consists of several parts working together.
26+
27+
## Health check
28+
On each of the two machines two scripts have to be added as `/usr/local/bin/check_adguard.sh`. These scripts act as a health script that keepalived uses to detect the availablity of the service.
29+
30+
On pi5-1:
31+
```bash
32+
#!/bin/bash
33+
UI_URL="http://192.168.100.18:853/"
34+
DNS_TEST="192.168.3.53"
35+
DNS_TEST_v6="fdd6:e6df:9a26:3::53"
36+
37+
# 1. Check UI
38+
if ! curl -fs --max-time 2 "$UI_URL" > /dev/null; then
39+
echo "AdGuardHome UI on Port 853 not reachable"
40+
exit 1
41+
fi
42+
43+
# 2. Check DNS IPv4
44+
if ! nc -zu -w2 "$DNS_TEST" 53 > /dev/null 2>&1; then
45+
echo "AdGuardHome DNS IPv4 not responding"
46+
exit 1
47+
fi
48+
49+
# 3. Check DNS IPv6
50+
if ! nc -6zu -w2 "$DNS_TEST_v6" 53 > /dev/null 2>&1; then
51+
echo "AdGuardHome DNS IPv6 not responding"
52+
exit 1
53+
fi
54+
55+
exit 0
56+
```
57+
58+
On pi4-1:
59+
```bash
60+
#!/bin/bash
61+
UI_URL="http://192.168.100.19:853/"
62+
DNS_TEST="192.168.3.54"
63+
DNS_TEST_v6="fdd6:e6df:9a26:3::54"
64+
65+
# 1. Check UI
66+
if ! curl -fs --max-time 2 "$UI_URL" > /dev/null; then
67+
echo "AdGuardHome UI on Port 853 not reachable"
68+
exit 1
69+
fi
70+
71+
# 2. Check DNS IPv4
72+
if ! nc -zu -w2 "$DNS_TEST" 53 > /dev/null 2>&1; then
73+
echo "AdGuardHome DNS IPv4 not responding"
74+
exit 1
75+
fi
76+
77+
# 3. Check DNS IPv6
78+
if ! nc -6zu -w2 "$DNS_TEST_v6" 53 > /dev/null 2>&1; then
79+
echo "AdGuardHome DNS IPv6 not responding"
80+
exit 1
81+
fi
82+
83+
exit 0
84+
```
85+
86+
Make the script executable on both machines using `sudo chmod +x /usr/local/bin/check_adguard.sh`.
87+
88+
Please note that I'm also checking the IPv6 connectivity. Reason for using netcat instead of e.g. dig is, that dig would cause frequent entries in AdGuardHome log, which annoyed me a lot. dig will provice the reliable results though, since you're doing DNS requests and can verify it is really working. It is even possible to create filter rules to avoid these requests showing up in your logs and statistics, but then I'm also excluding other local requests to the DNS so I'm ok with using netcat instead.
89+
90+
## keepalived configuration
91+
Now we'll have to add the configuration for keepalived.
92+
93+
On pi5-1:
94+
```bash
95+
global_defs {
96+
script_user root
97+
enable_script_security
98+
}
99+
100+
vrrp_script chk_adguard {
101+
script "/usr/local/bin/check_adguard.sh"
102+
interval 5
103+
weight -20
104+
}
105+
106+
vrrp_instance VI_DNS {
107+
state MASTER
108+
interface eth0.3
109+
virtual_router_id 51
110+
priority 100
111+
advert_int 1
112+
authentication {
113+
auth_type PASS
114+
auth_pass WENyvutQ
115+
}
116+
virtual_ipaddress {
117+
192.168.3.190/24
118+
}
119+
track_script {
120+
chk_adguard
121+
}
122+
}
123+
124+
vrrp_instance VI_UI {
125+
state MASTER
126+
interface eth0
127+
virtual_router_id 52
128+
priority 100
129+
advert_int 1
130+
authentication {
131+
auth_type PASS
132+
auth_pass WENyvutQ
133+
}
134+
virtual_ipaddress {
135+
192.168.100.190/24
136+
}
137+
track_script {
138+
chk_adguard
139+
}
140+
}
141+
```
142+
143+
On pi4-1:
144+
```bash
145+
global_defs {
146+
script_user root
147+
enable_script_security
148+
}
149+
150+
vrrp_script chk_adguard {
151+
script "/usr/local/bin/check_adguard.sh"
152+
interval 5
153+
weight -20
154+
}
155+
156+
vrrp_instance VI_DNS {
157+
state MASTER
158+
interface eth0.3
159+
virtual_router_id 51
160+
priority 90
161+
advert_int 1
162+
authentication {
163+
auth_type PASS
164+
auth_pass WENyvutQ
165+
}
166+
virtual_ipaddress {
167+
192.168.3.190/24
168+
}
169+
track_script {
170+
chk_adguard
171+
}
172+
}
173+
174+
vrrp_instance VI_UI {
175+
state MASTER
176+
interface eth0
177+
virtual_router_id 52
178+
priority 90
179+
advert_int 1
180+
authentication {
181+
auth_type PASS
182+
auth_pass WENyvutQ
183+
}
184+
virtual_ipaddress {
185+
192.168.100.190/24
186+
}
187+
track_script {
188+
chk_adguard
189+
}
190+
}
191+
```
192+
193+
It is important to put the block for the check `vrrp_script chk_adguard` in the front of the actual configuration, otherwise keepalived will complain and won't find it. The `auth_pass` should not exceed 8 characters and shouldn't be too complex. I've documented here only a randomly created password. You should change it to your needs.
194+
195+
The priority of the pi4-1 is set to 90, so by default this is the secondary instance.
196+
197+
Now restart the keepalived service using `sudo systemctl restart keepalived.service` on both machines. You can check the current state using `sudo systemctl status keepalived.service` so you'll see who is primary and who is secondary. The instance with the highest weight is the primary, and the health script reduces the weight by minus 20 if there's somekind of error.
198+
199+
## AdGuardHome
200+
I've had to change the `/opt/AdGuardHome/AdGuardHome.yaml` to bind the process to all available IPs, otherwise the process isn't able to bound to the Virtual IP (VIP) 192.168.100.190 and 192.168.3.190. The relevant parts are:
201+
202+
```yaml
203+
http:
204+
address: 0.0.0.0:853
205+
dns:
206+
bind_hosts:
207+
- 0.0.0.0
208+
port: 53
209+
```
210+
211+
Do a restart of AdGuardHome after this, using `sudo systemctl restart AdGuardHome.service` on both machines.
212+
213+
## Router
214+
Check if you're able to login to AdGuardHome on the VIP for the UI, e.g. http://192.168.100.190:853. If that is working, try to query the DNS under it's VIP, e.g.:
215+
216+
```bash
217+
dig google.com @192.168.3.190
218+
219+
; <<>> DiG 9.10.6 <<>> google.com @192.168.3.190
220+
;; global options: +cmd
221+
;; Got answer:
222+
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37929
223+
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
224+
225+
;; OPT PSEUDOSECTION:
226+
; EDNS: version: 0, flags:; udp: 4096
227+
;; QUESTION SECTION:
228+
;google.com. IN A
229+
230+
;; ANSWER SECTION:
231+
google.com. 37 IN A 142.250.179.142
232+
233+
;; Query time: 36 msec
234+
;; SERVER: 192.168.3.190#53(192.168.3.190)
235+
;; WHEN: Sun Oct 05 08:33:24 CEST 2025
236+
;; MSG SIZE rcvd: 55
237+
```
238+
239+
# Problems with IPv6
240+
What's currently unresolved is to set a IPv6 address for the DNS. It looks like the Rasbpian version of keepalived doesn't support this out of the box. There are workarounds for this but I don't know if I really want to do this for now, as it doesn't offer me any benefits.
241+
242+
# Conclusion
243+
It was quite simple to get this configuration running and I'm happy that the fallback works this good. I don't have to fear system restarts that block my complete network until completed startup and at the same time I'm having more control over the DNS requests in my home network.
244+
With the help of this setup I was able to identify:
245+
* Tasmota devices can be configured to use a local ntp server instead of making requests to the outside
246+
* My Feinstaubsensor is unable to change it's ntp server
247+
* many requests are now cached and doesn't rely that much on external requests, speeding up page loading

0 commit comments

Comments
 (0)