Secure Dynamic DNS with Cloudflare and 1Password

• DevOps, Security

Setting up dynamic DNS for home servers while keeping API credentials secure using 1Password service accounts.

The Challenge

Home internet connections typically have dynamic IP addresses that change periodically. If you're running services (like a media server) that need to be accessible via a domain name, you need a way to automatically update DNS records when your IP changes.

Traditional DDNS services work, but using Cloudflare gives you more control, better security features, and integration with your existing DNS setup.

Security First: Why Not Store API Keys on Disk?

Most DDNS scripts store API credentials in plain text files. This creates security risks:

  • Credentials visible in process lists
  • Accessible to any user with file system access
  • Risk of accidental commits to version control
  • No audit trail for credential access

The Solution: 1Password Service Accounts

1Password service accounts are designed for automated systems. They provide:

  • Programmatic access to secrets without user interaction
  • Audit logging of credential access
  • Centralized credential management
  • Easy credential rotation

Setup Steps

1. Create Cloudflare API Token

In Cloudflare dashboard → My Profile → API Tokens:

  1. Create Token → Custom token
  2. Permissions:
    • Zone:Zone:Read
    • Zone:DNS:Edit
  3. Zone Resources: Include → Specific zone → your domain
  4. Copy the generated token

2. Store Credentials in 1Password

  1. Create a dedicated vault for automation credentials (e.g., "dev" vault) Note: Service accounts may not have access to your default "Personal" vault. Creating a separate vault specifically for automation credentials provides better security isolation.
  2. Create new Secure Note item named "cloudflare" in the dedicated vault
  3. Add fields:
    • zone_id: From Cloudflare dashboard sidebar
    • api_token: The token from step 1

3. Create 1Password Service Account

  1. Go to 1Password → Settings → Developer → Service Accounts
  2. Create service account with vault access to your dedicated vault (not Personal vault)
  3. Grant read-only access to the specific vault containing your credentials
  4. Save the service account token (starts with ops_)

4. Install 1Password CLI

curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg

echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] \
https://downloads.1password.com/linux/debian/amd64 stable main' | \
sudo tee /etc/apt/sources.list.d/1password.list

sudo apt update && sudo apt install 1password-cli

The DDNS Script

Create ~/.local/bin/update-ddns.sh:

#!/bin/bash

source ~/.env

# Authenticate with 1Password service account
export OP_SERVICE_ACCOUNT_TOKEN

DOMAIN="yourdomain.com"
SUBDOMAIN="movies"  # Creates movies.yourdomain.com
ZONE_ID=$(op read "op://dev/cloudflare/zone_id")
API_TOKEN=$(op read "op://dev/cloudflare/api_token")

IP=$(curl -s ifconfig.me)

# Get DNS record ID
RESPONSE=$(curl -s -X GET \
"https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$SUBDOMAIN.$DOMAIN" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json")

RECORD_ID=$(echo $RESPONSE | jq -r '.result[0].id')

if [ "$RECORD_ID" = "null" ]; then
# Create new DNS record
echo "Creating new DNS record..."
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$SUBDOMAIN\",\"content\":\"$IP\",\"ttl\":1}"
else
# Update existing DNS record
echo "Updating existing DNS record..."
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$SUBDOMAIN\",\"content\":\"$IP\",\"ttl\":1}"
fi

Make it executable: chmod +x ~/.local/bin/update-ddns.sh

Environment Configuration

Create ~/.env with your service account token:

OP_SERVICE_ACCOUNT_TOKEN=ops_your_service_account_token_here

Secure the file: chmod 600 ~/.env

Automation with Cron

Add to crontab (crontab -e) to check every 10 minutes:

*/10 * * * * /home/username/.local/bin/update-ddns.sh >/dev/null 2>&1

How It Works

  1. Script sources environment variables (including service account token)
  2. Uses 1Password CLI to fetch API credentials securely
  3. Gets current public IP address
  4. Checks if DNS record exists, creates or updates accordingly
  5. Runs automatically via cron without human intervention

Advantages of This Approach

  • Security: API credentials never stored on disk
  • Auditability: 1Password logs all credential access
  • Maintainability: Easy to rotate credentials
  • Reliability: Handles both record creation and updates
  • Flexibility: Works with Cloudflare's full feature set

Troubleshooting

Permission Errors

Ensure your API token has Zone:DNS:Edit permissions, not just read access.

Service Account Issues

Verify the service account has access to the correct vault containing your credentials.

Testing

Run the script manually first to verify it works before adding to cron.