cmc/cleberg.net
My personal web garden & blog.
clone: git clone https://gitbay.org/cmc/cleberg.net.git
main: content/blog/2024-03-15-self-hosting-ddns-updater.org · raw
1#+date: [2024-03-15 Fri 00:00:00]
2#+title: Self-Hosting Guide: DDNS Updater
3#+description: Deploying a DDNS updater with Docker Compose and Cloudflare.
4#+slug: self-hosting-ddns-updater
5#+filetags: :linux:self-hosting:
6
7#+caption: DDNS Updater Web View
8#+attr_html: :alt An example of the DDNS Updater app running and showing that cleberg.net's IP has been updated on Cloudflare.
9[[https://img.cleberg.net/blog/20240315-ddns-updater/ddns.webp]]
10
11[[https://github.com/qdm12/ddns-updater][DDNS Updater]] is a program to keep DNS A and/or AAAA records updated for multiple
12DNS providers.
13
14If you've read any of my other posts, you'll notice that I have been searching
15for and using a few different DDNS updating solutions for years. You'll also
16notice that I love any projects that offer a Docker Compose solution.
17
18Luckily, DDNS Updater fits both of these preferences.
19
20** Installation
21
22To get started, always make sure to review the project's [[https://github.com/qdm12/ddns-updater/blob/master/README.md][README]]. I'll be
23documenting my steps below, but they may have changed by the time you read this.
24
25The first step is to set up the directories and files required for the project.
26
27#+begin_src sh
28mkdir ~/ddns-updater
29mkdir ~/ddns-updater/data
30touch ~/ddns-updater/data/config.json
31#+end_src
32
33*** Configuration
34
35The main configuration you need to update is the =data/config.json= file. There
36is a large list of supported providers in the README, but I'm going to use
37Cloudflare in this example.
38
39#+begin_src sh
40nano ~/ddns-updater/data/config.json
41#+end_src
42
43When setting up the configuration for Cloudflare, you'll need the following:
44
45- Required Parameters
46 - ="zone_identifier"= is the Zone ID of your site from the domain overview
47 page
48 - ="host"= is your host and can be ="@"=, a subdomain or the wildcard ="*"=.
49 See [[https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949][this issue comment for context]].
50 - ="ttl"= integer value for record TTL in seconds (specify 1 for automatic)
51 - One of the following ([[https://developers.cloudflare.com/fundamentals/api/get-started/][how to find API keys]]):
52 - Email ="email"= and Global API Key ="key"=
53 - User service key ="user_service_key"=
54 - API Token ="token"=, configured with DNS edit permissions for your DNS
55 name's zone
56- Optional Parameters
57 - ="proxied"= can be set to =true= to use the proxy services of Cloudflare
58 - ="ip_version"= can be =ipv4= (A records), or =ipv6= (AAAA records) or =ipv4
59 or ipv6= (update one of the two, depending on the public ip found). It
60 defaults to =ipv4 or ipv6=.
61 - ="ipv6_suffix"= is the IPv6 interface identifier suffix to use. It can be
62 for example =0:0:0:0:72ad:8fbb:a54e:bedd/64=. If left empty, it defaults to
63 no suffix and the raw public IPv6 address obtained is used in the record
64 updating.
65
66#+begin_src conf
67{
68 "settings": [
69 {
70 "provider": "cloudflare",
71 "zone_identifier": "some id",
72 "domain": "domain.com",
73 "host": "@",
74 "ttl": 1,
75 "proxied": true,
76 "token": "yourtoken",
77 "ip_version": "ipv4",
78 "ipv6_suffix": ""
79 }
80 ]
81}
82#+end_src
83
84Once you have configured the provider of your choice, correct the file and
85directory permissions and ownership.
86
87#+begin_src sh
88cd ~/ddns_updater
89# Owned by user ID of Docker container (1000)
90chown -R 1000 data
91# all access (for creating json database file data/updates.json)
92chmod 700 data
93# read access only
94chmod 400 data/config.json
95#+end_src
96
97*** Docker Compose
98
99After creating the project structure, let's create the =docker-compose.yml=
100file.
101
102#+begin_src sh
103nano ~/ddns_-pdater/docker-compose.yml
104#+end_src
105
106#+begin_src config
107version: "3.7"
108services:
109 ddns-updater:
110 image: qmcgaw/ddns-updater
111 container_name: ddns-updater
112 network_mode: bridge
113 ports:
114 - 8097:8000/tcp # Change the 8097 value to whichever port you want to use
115 volumes:
116 - ./data:/updater/data
117 environment:
118 - CONFIG=
119 - PERIOD=5m
120 - UPDATE_COOLDOWN_PERIOD=5m
121 - PUBLICIP_FETCHERS=all
122 - PUBLICIP_HTTP_PROVIDERS=all
123 - PUBLICIPV4_HTTP_PROVIDERS=all
124 - PUBLICIPV6_HTTP_PROVIDERS=all
125 - PUBLICIP_DNS_PROVIDERS=all
126 - PUBLICIP_DNS_TIMEOUT=3s
127 - HTTP_TIMEOUT=10s
128
129 # Web UI
130 - LISTENING_ADDRESS=:8000
131 - ROOT_URL=/
132
133 # Backup
134 - BACKUP_PERIOD=0 # 0 to disable
135 - BACKUP_DIRECTORY=/updater/data
136
137 # Other
138 - LOG_LEVEL=info
139 - LOG_CALLER=hidden
140 - SHOUTRRR_ADDRESSES=
141 restart: always
142#+end_src
143
144After configuring your preferences in the =docker-compose.yml=, launch the
145container.
146
147#+begin_src sh
148cd ~/ddns-updater
149sudo docker-compose up -d
150#+end_src
151
152If you've launched this on your local machine, you can launch =localhost:8097=
153in your browser to see the results.
154
155*** Nginx Reverse Proxy
156
157If you launched this service on a server, other machine, or just want to access
158it remotely via a domain name, you can use Nginx as a reverse proxy to expose
159the service publicly.
160
161Start by creating the Nginx configuration file.
162
163#+begin_src sh
164sudo nano /etc/nginx/sites-available/ddns
165#+end_src
166
167Here's a basic example that should work properly.
168
169#+begin_src conf
170server {
171 # If using 443, remember to include your ssl_certificate
172 # and ssl_certificate_key
173 listen [::]:80;
174 listen 80;
175 server_name ddns.example.com;
176
177 location / {
178 set $upstream_ao http://127.0.0.1:9380;
179 proxy_pass $upstream_ao;
180
181 # May need some additional proxy_* parameters,
182 # see the full example below if necessary
183 }
184}
185#+end_src
186
187Here's a full example that uses my Authelia authentication service to require
188authentication before someone can access the web page.
189
190#+begin_src conf
191server {
192 if ($host ~ ^[^.]+\.example\.com$) {
193 return 301 https://$host$request_uri;
194 }
195
196 listen [::]:80;
197 listen 80;
198 server_name ddns.example.com;
199 return 404;
200}
201
202server {
203 listen [::]:443 ssl http2;
204 listen 443 ssl http2;
205 server_name ddns.example.com;
206 access_log /var/log/nginx/ddns.access.log;
207 error_log /var/log/nginx/ddns.error.log;
208
209 add_header X-Content-Type-Options "nosniff";
210 add_header X-XSS-Protection "1; mode=block";
211 add_header X-Frame-Options "DENY";
212 add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
213 add_header Referrer-Policy "no-referrer";
214
215 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
216 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
217 include /etc/letsencrypt/options-ssl-nginx.conf;
218 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
219
220 location /authelia {
221 internal;
222 set $upstream_authelia http://127.0.0.1:9091/api/verify; #change the IP and Port to match the IP and Port of your Authelia container
223 proxy_pass_request_body off;
224 proxy_pass $upstream_authelia;
225 proxy_set_header Content-Length "";
226
227 # Timeout if the real server is dead
228 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
229 client_body_buffer_size 128k;
230 proxy_set_header Host $host;
231 proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
232 proxy_set_header X-Real-IP $remote_addr;
233 proxy_set_header X-Forwarded-For $remote_addr;
234 proxy_set_header X-Forwarded-Proto $scheme;
235 proxy_set_header X-Forwarded-Host $http_host;
236 proxy_set_header X-Forwarded-Uri $request_uri;
237 proxy_set_header X-Forwarded-Ssl on;
238 proxy_redirect http:// $scheme://;
239 proxy_http_version 1.1;
240 proxy_set_header Connection "";
241 proxy_cache_bypass $cookie_session;
242 proxy_no_cache $cookie_session;
243 proxy_buffers 4 32k;
244
245 send_timeout 5m;
246 proxy_read_timeout 240;
247 proxy_send_timeout 240;
248 proxy_connect_timeout 240;
249 }
250
251 location / {
252 set $upstream_ddns http://127.0.0.1:8097; #change ddns to match your container name: $upstream_some-container-name or $upstream_somecontainername
253 proxy_pass $upstream_ddns; #change ddns to match your container name: $upstream_some-container-name or $upstream_somecontainername
254
255 auth_request /authelia;
256 auth_request_set $target_url https://$http_host$request_uri;
257 auth_request_set $user $upstream_http_remote_user;
258 auth_request_set $email $upstream_http_remote_email;
259 auth_request_set $groups $upstream_http_remote_groups;
260 proxy_set_header Remote-User $user;
261 proxy_set_header Remote-Email $email;
262 proxy_set_header Remote-Groups $groups;
263
264 error_page 401 =302 https://auth.example.com/?rd=$target_url; #change this to match your authentication domain/subdomain
265
266 client_body_buffer_size 128k;
267
268 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
269
270 send_timeout 5m;
271 proxy_read_timeout 360;
272 proxy_send_timeout 360;
273 proxy_connect_timeout 360;
274
275 proxy_set_header Host $host;
276 proxy_set_header Upgrade $http_upgrade;
277 proxy_set_header Connection upgrade;
278 proxy_set_header Accept-Encoding gzip;
279 proxy_set_header X-Real-IP $remote_addr;
280 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
281 proxy_set_header X-Forwarded-Proto $scheme;
282 proxy_set_header X-Forwarded-Host $http_host;
283 proxy_set_header X-Forwarded-Uri $request_uri;
284 proxy_set_header X-Forwarded-Ssl on;
285 proxy_redirect http:// $scheme://;
286 proxy_http_version 1.1;
287 proxy_set_header Connection "";
288 proxy_cache_bypass $cookie_session;
289 proxy_no_cache $cookie_session;
290 proxy_buffers 64 256k;
291
292 # set_real_ip_from 192.168.1.0/16; #make sure this matches your network setup
293 # real_ip_header CF-Connecting-IP;
294 # real_ip_recursive on;
295 }
296}
297#+end_src
298
299When complete, simply link the file and restart the web server.
300
301#+begin_src sh
302sudo ln -s /etc/nginx/sites-available/ddns /etc/nginx/sites-enabled/ddns
303sudo systemctl restart nginx.service
304#+end_src
305
306Your ddns-updater service will now be available via =ddns.example.com=!