2025-11-24 12:56:35 -05:00
# Porkbun Dynamic DNS Update Script
A bash script for automatically updating DNS A records on Porkbun with your current public IP address. Supports multi-machine failover configurations where multiple servers can independently update their own A records for the same domain.
## Features
- ✅ **Automatic IP Detection** - Uses Porkbun's API and multiple fallback methods
- ✅ **Multi-Machine Failover** - Multiple machines can maintain A records for the same domain
- ✅ **Machine Identification** - Uses notes field to track which machine owns each record
- ✅ **Secure** - API credentials stored in `.env` file (excluded from git)
- ✅ **Error Handling** - Comprehensive validation and error messages
- ✅ **Wildcard Support** - Updates both root and wildcard DNS records
## Requirements
- `bash` shell
- `curl` - for API calls
- `jq` - for JSON parsing
- Porkbun API access enabled for your domain
## Installation
1. Clone or download this script to your machine
2. Make the script executable:
```bash
chmod +x updateDNS.sh
```
3. Copy the `.env` file and configure it (see Configuration below)
## Configuration
Edit the `.env` file with your settings:
```bash
# Required: Your Porkbun API credentials
API_KEY=pk1_your_api_key_here
SECRET_KEY=sk1_your_secret_key_here
# Required: The domain to update
DOMAIN=example.com
2025-11-24 13:17:00 -05:00
# Required: Name of the machine for tracking
MACHINE_ID="exmample_machine"
2025-11-24 12:56:35 -05:00
```
### Getting Porkbun API Credentials
1. Log in to your [Porkbun account ](https://porkbun.com/account/api )
2. Navigate to **Account > API Access**
3. Generate API keys for your domain
4. **Important** : Enable API access for your specific domain in the domain settings
## Usage
### Single Machine (Simple Dynamic DNS)
Basic configuration on one machine:
```bash
# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=home-server
```
Run the script:
```bash
./updateDNS.sh
```
This will update:
- `example.com` → Your current public IP
- `*.example.com` → Your current public IP
### Multi-Machine Failover Setup
Configure multiple machines with **the same domain** but **different MACHINE_IDs** :
**Server 1:**
```bash
# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=server1
```
**Server 2:**
```bash
# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=server2
```
**Server 3:**
```bash
# .env
API_KEY=pk1_xxxxx
SECRET_KEY=sk1_xxxxx
DOMAIN=example.com
MACHINE_ID=backup-server
```
Each machine runs the same script independently. The result will be:
```
example.com. 300 IN A 203.0.113.10 (server1's IP)
example.com. 300 IN A 198.51.100.20 (server2's IP)
example.com. 300 IN A 192.0.2.30 (backup-server's IP)
```
DNS clients will receive all three IPs, providing:
- **Automatic failover** - if one server is down, clients use others
- **Load distribution** - traffic is distributed across servers
- **Independent updates** - each machine manages only its own record
### Managed records and notes tagging
The script only manages records that it created itself, identified via the DNS **notes** field.
- Internally it uses a managed-notes prefix:
```bash
NOTES_PREFIX="ddns:"
```
- For each machine, the script sets the notes value to:
```text
ddns:< MACHINE_ID >
```
- When cleaning up or checking for duplicates, it **only** looks at records where:
- `name` matches the FQDN being updated (e.g. `example.com` or `*.example.com` ), and
- `type` is `A` , `ALIAS` , or `CNAME` , and
- `notes == "ddns:<MACHINE_ID>"`
This means:
- Manually managed records without this prefix are left untouched.
- Multiple machines (different `MACHINE_ID` s) can safely coexist on the same domain.
- If you ever want to manually take control of a record that was created by this script,
you can remove or change the `ddns:<MACHINE_ID>` note and the script will stop managing it.
## Dry-run mode
You can run the script in **dry-run** mode to see what it would do without actually
creating or deleting any DNS records.
Set the `DRY_RUN` environment variable to `1` when invoking the script:
```bash
DRY_RUN=1 ./updateDNS.sh
```
In dry-run mode:
- The script still loads configuration and detects your public IP.
- It still retrieves and displays existing DNS records.
- It logs which records **would** be deleted and what **would** be created.
- It does **not** call Porkbun's `dns/delete` or `dns/create` endpoints.
This is useful when first configuring the script or making changes to your DNS setup,
so you can verify behavior before applying it.
2025-11-24 13:17:00 -05:00
## Systemd integration helpers
This repository also includes helper scripts to install the updater as a systemd service,
uninstall it cleanly, and view logs more easily.
### `install.sh` – install systemd timer + service
`install.sh` sets up a `porkbun-ddns` systemd service and timer that will run
`updateDNS.sh` for you on a schedule (default: every 5 minutes).
What it does:
- Verifies required files exist in the current directory:
- `updateDNS.sh`
- `.env`
- `porkbun-ddns.service`
- `porkbun-ddns.timer`
- Ensures `updateDNS.sh` is executable.
- Checks for required dependencies: `curl` , `jq` , `systemctl` .
- Validates that `.env` contains `API_KEY` , `SECRET_KEY` , `DOMAIN` , and `MACHINE_ID` .
- Runs `updateDNS.sh` once as a test (with your current config).
- Installs and enables:
- `/etc/systemd/system/porkbun-ddns.service`
- `/etc/systemd/system/porkbun-ddns.timer`
- Starts the timer and shows its status.
Usage:
```bash
cd /path/to/porkbun
./install.sh
```
Notes:
- Do **not** run `install.sh` directly as root; it will use `sudo` when needed.
- The service runs as the current user (it rewrites `User=` and `Group=` in
the service file to match who ran `install.sh` ).
After installation, some useful commands:
```bash
systemctl status porkbun-ddns.timer
systemctl status porkbun-ddns.service
journalctl -u porkbun-ddns.service -n 50
```
### `uninstall.sh` – remove systemd timer + service
`uninstall.sh` cleanly removes the `porkbun-ddns` systemd units while leaving your
scripts and configuration files intact.
What it does:
- Stops `porkbun-ddns.timer` and `porkbun-ddns.service` if they are running.
- Disables the timer so it no longer starts at boot.
- Removes `/etc/systemd/system/porkbun-ddns.service` and
`/etc/systemd/system/porkbun-ddns.timer` .
- Reloads systemd and resets any failed state for these units.
Usage:
```bash
cd /path/to/porkbun
./uninstall.sh
```
Notes:
- Do **not** run `uninstall.sh` as root; it will use `sudo` for systemd operations.
- Your local files remain:
- `updateDNS.sh`
- `.env`
- `porkbun-ddns.service`
- `porkbun-ddns.timer`
so you can reinstall later with `./install.sh` .
### `logs.sh` – convenient log viewer
`logs.sh` is a small wrapper around `journalctl` and `systemctl` to make it easier
to inspect the `porkbun-ddns.service` logs and status.
Basic usage (show last 50 lines):
```bash
cd /path/to/porkbun
./logs.sh
```
Common options:
- Follow logs in real-time (like `tail -f` ):
```bash
./logs.sh -f
```
- Show last N lines:
```bash
./logs.sh -n 100
```
- Show logs from today only:
```bash
./logs.sh -t
```
- Show logs from the last hour:
```bash
./logs.sh -h
```
- Show only error messages:
```bash
./logs.sh -e
```
- Show service and timer status plus recent runs:
```bash
./logs.sh -s
```
Run `./logs.sh --help` for the full list of options.
2025-11-24 12:56:35 -05:00
## How It Works
1. **Load Configuration** - Reads API credentials and settings from `.env`
2. **Detect Public IP** - Uses Porkbun's `/ping` endpoint or fallback services (api.ipify.org, icanhazip.com)
3. **Retrieve DNS Records** - Gets all existing DNS records for the domain and displays them
4. **Check for Duplicates** - Detects if multiple records exist for this machine ID
5. **Skip if Unchanged** - If IP matches and exactly one record exists, skips update
2025-11-24 13:17:00 -05:00
6. **Delete Own Records** - Removes this machine's previous A records (by matching notes field)
7. **Verify Cleanup** - Ensures all old records were deleted before creating new ones
8. **Create New Record** - Adds fresh A record with current IP and machine ID in notes
9. **Verify Creation** - Confirms exactly one record exists for this machine
2025-11-24 12:56:35 -05:00
2025-11-24 13:17:00 -05:00
> Note: The script only deletes and verifies records that it manages
2025-11-24 12:56:35 -05:00
> (those whose notes are exactly `ddns:<MACHINE_ID>`). Other records in the
> same zone are left untouched.
## Security Best Practices
### Protect Your API Keys
1. **Never commit `.env` to version control** :
```bash
echo "*.env" >> .gitignore
echo ".env" >> .gitignore
```
2. **Rotate your API keys regularly** in the Porkbun dashboard
3. **Use restrictive file permissions** :
```bash
chmod 600 .env
```
4. **Use different API keys** for development/production if possible
### If You Accidentally Exposed Keys
1. **Immediately revoke** the API keys in your Porkbun account
2. **Generate new keys** and update your `.env` file
## Troubleshooting
### Error: "API access not enabled"
1. Log in to Porkbun
2. Go to your domain settings
3. Enable API access for the specific domain
4. Wait a few minutes for changes to propagate
### Error: "Failed to create DNS record"
- Check that API credentials are correct
- Verify API access is enabled for your domain
- Ensure MACHINE_ID doesn't contain special characters
- Check that you're not exceeding Porkbun's rate limits
### Multiple machines overwriting each other
- Make sure each machine has a **unique MACHINE_ID**
- Verify each machine is using the latest version of the script
- Check DNS records in Porkbun dashboard to see notes field (look for `ddns:<MACHINE_ID>` )
### IP not updating
2025-11-24 13:17:00 -05:00
- Check script is running
2025-11-24 12:56:35 -05:00
- Verify your public IP actually changed
- Check Porkbun API status
- Review logs for error messages
## API Rate Limits
Porkbun's API has rate limits. For dynamic DNS:
- **Recommended**: Run every 5-15 minutes
- **Don't**: Run more than once per minute
- The script only makes API calls when it runs, not continuously
## Example Output
Example output with the new structured logging format:
```text
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Using environment file: /home/user/porkbun/.env
IP source: Porkbun API (/ping)
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Current Public IP: 45.73.134.145
2025-11-24 13:17:00 -05:00
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: EXAMPLE_MACHINE
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Domain: example.com
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Processing DNS record for: example.com (IP: 45.73.134.145)
Mon Nov 24 12:04:38 PM EST 2025 [INFO] Machine ID: EXAMPLE_MACHINE
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Existing records for FQDN 'example.com':
Type: A, Name: example.com, Content: 45.73.134.145, Notes: ddns:EXAMPLE_MACHINE, Managed: true, ID: 508029248
Type: NS, Name: example.com, Content: curitiba.porkbun.com, Notes: none, Managed: false, ID: 506591857
Type: NS, Name: example.com, Content: fortaleza.porkbun.com, Notes: none, Managed: false, ID: 506591856
Type: NS, Name: example.com, Content: maceio.porkbun.com, Notes: none, Managed: false, ID: 506591854
Type: NS, Name: example.com, Content: salvador.porkbun.com, Notes: none, Managed: false, ID: 506591855
2025-11-24 12:56:35 -05:00
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Record already exists with correct IP (45.73.134.145). No update needed.
2025-11-24 13:17:00 -05:00
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Processing DNS record for: *.example.com (IP: 45.73.134.145)
Mon Nov 24 12:04:39 PM EST 2025 [INFO] Machine ID: EXAMPLE_MACHINE
Mon Nov 24 12:04:40 PM EST 2025 [INFO] Existing records for FQDN '*.example.com':
Type: A, Name: *.example.com, Content: 45.73.134.145, Notes: ddns:EXAMPLE_MACHINE, Managed: true, ID: 508029259
2025-11-24 12:56:35 -05:00
Mon Nov 24 12:04:40 PM EST 2025 [INFO] Record already exists with correct IP (45.73.134.145). No update needed.
Mon Nov 24 12:04:40 PM EST 2025 [INFO] DNS update completed successfully
```
## Files
- `updateDNS.sh` - Main script
- `.env` - Configuration file (contains secrets, **DO NOT COMMIT** )
- `README.md` - This file
## License
This script is provided as-is for use with Porkbun DNS services.
## Contributing
Improvements welcome! Consider adding:
- IPv6 support (AAAA records)
- Email notifications on IP change
2025-11-24 13:17:00 -05:00
- Health check endpoint