#+date: <2024-03-15> #+title: Self-Hosting DDNS Updater #+description: #+slug: self-hosting-ddns-updater #+caption: DDNS Updater Web View [[https://media.githubusercontent.com/media/ccleberg/img/main/blog/20240315-ddns-updater/ddns.png]] [[https://github.com/qdm12/ddns-updater][DDNS Updater]] is a program to keep DNS A and/or AAAA records updated for multiple DNS providers. If you've read any of my other posts, you'll notice that I have been searching for and using a few different DDNS updating solutions for years. You'll also notice that I love any projects that offer a Docker Compose solution. Luckily, DDNS Upater fits both of these preferences. ** Installation To get started, always make sure to review the project's [[https://github.com/qdm12/ddns-updater/blob/master/README.md][README]]. I'll be documenting my steps below, but they may have changed by the time you read this. The first step is to set up the directories and files required for the project. #+begin_src sh mkdir ~/ddns-updater mkdir ~/ddns-updater/data touch ~/ddns-updater/data/config.json #+end_src *** Configuration The main configuration you need to update is the =data/config.json= file. There is a large list of supported providers in the README, but I'm going to use Cloudflare in this example. #+begin_src sh nano ~/ddns-updater/data/config.json #+end_src When setting up the configuration for Cloudflare, you'll need the following: - Required Parameters - ="zone_identifier"= is the Zone ID of your site from the domain overview page - ="host"= is your host and can be ="@"=, a subdomain or the wildcard ="*"=. See [[https://github.com/qdm12/ddns-updater/issues/243#issuecomment-928313949][this issue comment for context]]. - ="ttl"= integer value for record TTL in seconds (specify 1 for automatic) - One of the following ([[https://developers.cloudflare.com/fundamentals/api/get-started/][how to find API keys]]): - Email ="email"= and Global API Key ="key"= - User service key ="user_service_key"= - API Token ="token"=, configured with DNS edit permissions for your DNS name's zone - Optional Parameters - ="proxied"= can be set to =true= to use the proxy services of Cloudflare - ="ip_version"= can be =ipv4= (A records), or =ipv6= (AAAA records) or =ipv4 or ipv6= (update one of the two, depending on the public ip found). It defaults to =ipv4 or ipv6=. - ="ipv6_suffix"= is the IPv6 interface identifier suffix to use. It can be for example =0:0:0:0:72ad:8fbb:a54e:bedd/64=. If left empty, it defaults to no suffix and the raw public IPv6 address obtained is used in the record updating. #+begin_src conf { "settings": [ { "provider": "cloudflare", "zone_identifier": "some id", "domain": "domain.com", "host": "@", "ttl": 1, "proxied": true, "token": "yourtoken", "ip_version": "ipv4", "ipv6_suffix": "" } ] } #+end_src Once you have configured the provider of your choice, correct the file and directory permissions and ownership. #+begin_src sh cd ~/ddns_updater # Owned by user ID of Docker container (1000) chown -R 1000 data # all access (for creating json database file data/updates.json) chmod 700 data # read access only chmod 400 data/config.json #+end_src *** Docker Compose After creating the project structure, let's create the =docker-compose.yml= file. #+begin_src sh nano ~/ddns_-pdater/docker-compose.yml #+end_src #+begin_src config version: "3.7" services: ddns-updater: image: qmcgaw/ddns-updater container_name: ddns-updater network_mode: bridge ports: - 8097:8000/tcp # Change the 8097 value to whichever port you want to use volumes: - ./data:/updater/data environment: - CONFIG= - PERIOD=5m - UPDATE_COOLDOWN_PERIOD=5m - PUBLICIP_FETCHERS=all - PUBLICIP_HTTP_PROVIDERS=all - PUBLICIPV4_HTTP_PROVIDERS=all - PUBLICIPV6_HTTP_PROVIDERS=all - PUBLICIP_DNS_PROVIDERS=all - PUBLICIP_DNS_TIMEOUT=3s - HTTP_TIMEOUT=10s # Web UI - LISTENING_ADDRESS=:8000 - ROOT_URL=/ # Backup - BACKUP_PERIOD=0 # 0 to disable - BACKUP_DIRECTORY=/updater/data # Other - LOG_LEVEL=info - LOG_CALLER=hidden - SHOUTRRR_ADDRESSES= restart: always #+end_src After configuring your preferences in the =docker-compose.yml=, launch the container. #+begin_src sh cd ~/ddns-updater sudo docker-compose up -d #+end_src If you've launched this on your local machine, you can launch =localhost:8097= in your browser to see the results. *** Nginx Reverse Proxy If you launched this service on a server, other machine, or just want to access it remotely via a domain name, you can use Nginx as a reverse proxy to expose the service publicly. Start by creating the Nginx configuration file. #+begin_src sh sudo nano /etc/nginx/sites-available/ddns #+end_src Here's a basic example that should work properly. #+begin_src conf server { # If using 443, remember to include your ssl_certificate # and ssl_certificate_key listen [::]:80; listen 80; server_name ddns.example.com; location / { set $upstream_ao http://127.0.0.1:9380; proxy_pass $upstream_ao; # May need some additional proxy_* parameters, # see the full example below if necessary } } #+end_src Here's a full example that uses my Authelia authentication service to require authentication before someone can access the web page. #+begin_src conf server { if ($host ~ ^[^.]+\.example\.com$) { return 301 https://$host$request_uri; } listen [::]:80; listen 80; server_name ddns.example.com; return 404; } server { listen [::]:443 ssl http2; listen 443 ssl http2; server_name ddns.example.com; access_log /var/log/nginx/ddns.access.log; error_log /var/log/nginx/ddns.error.log; add_header X-Content-Type-Options "nosniff"; add_header X-XSS-Protection "1; mode=block"; add_header X-Frame-Options "DENY"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; add_header Referrer-Policy "no-referrer"; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; location /authelia { internal; 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 proxy_pass_request_body off; proxy_pass $upstream_authelia; proxy_set_header Content-Length ""; # Timeout if the real server is dead proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; client_body_buffer_size 128k; proxy_set_header Host $host; proxy_set_header X-Original-URL $scheme://$http_host$request_uri; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Uri $request_uri; proxy_set_header X-Forwarded-Ssl on; proxy_redirect http:// $scheme://; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_cache_bypass $cookie_session; proxy_no_cache $cookie_session; proxy_buffers 4 32k; send_timeout 5m; proxy_read_timeout 240; proxy_send_timeout 240; proxy_connect_timeout 240; } location / { set $upstream_ddns http://127.0.0.1:8097; #change ddns to match your container name: $upstream_some-container-name or $upstream_somecontainername proxy_pass $upstream_ddns; #change ddns to match your container name: $upstream_some-container-name or $upstream_somecontainername auth_request /authelia; auth_request_set $target_url https://$http_host$request_uri; auth_request_set $user $upstream_http_remote_user; auth_request_set $email $upstream_http_remote_email; auth_request_set $groups $upstream_http_remote_groups; proxy_set_header Remote-User $user; proxy_set_header Remote-Email $email; proxy_set_header Remote-Groups $groups; error_page 401 =302 https://auth.example.com/?rd=$target_url; #change this to match your authentication domain/subdomain client_body_buffer_size 128k; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503; send_timeout 5m; proxy_read_timeout 360; proxy_send_timeout 360; proxy_connect_timeout 360; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Accept-Encoding gzip; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Uri $request_uri; proxy_set_header X-Forwarded-Ssl on; proxy_redirect http:// $scheme://; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_cache_bypass $cookie_session; proxy_no_cache $cookie_session; proxy_buffers 64 256k; # set_real_ip_from 192.168.1.0/16; #make sure this matches your network setup # real_ip_header CF-Connecting-IP; # real_ip_recursive on; } } #+end_src When complete, simply link the file and restart the web server. #+begin_src sh sudo ln -s /etc/nginx/sites-available/ddns /etc/nginx/sites-enabled/ddns sudo systemctl restart nginx.service #+end_src Your ddns-updater service will now be available via =ddns.example.com=!