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