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