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=!