Pick your OS / firewall. Each setup is a drop-in script plus a cron / scheduled-task line. You already have your auth URL + server URL. Paste the server key in the script, save, schedule, done.
Tested on Ubuntu 22.04 / 24.04, Debian 12. The most common modern Linux setup.
Drop this at /usr/local/bin/ipauth-update.sh. The SERVER_URL below carries your server key; change PORT if you're not gating SSH. The state file at /var/cache/ipauth-last tracks the last IP we added so we can delete it cleanly. Your firewall never accumulates stale entries.
#!/bin/sh
SERVER_URL="https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
PORT=22
STATE=/var/cache/ipauth-last
CURIP=$(curl --max-time 5 -fsS "$SERVER_URL" | grep -oE '"ipaddress":"[0-9.]+' | cut -d'"' -f4)
[ -z "$CURIP" ] && exit 0
OLDIP=$(cat "$STATE" 2>/dev/null)
[ "$CURIP" = "$OLDIP" ] && exit 0 # unchanged → no-op
# Remove the previous allow (if any), then add the current one.
[ -n "$OLDIP" ] && ufw delete allow from "$OLDIP" to any port "$PORT" proto tcp >/dev/null 2>&1
ufw allow from "$CURIP" to any port "$PORT" proto tcp comment "ipauth"
echo "$CURIP" > "$STATE"
sudo chmod +x /usr/local/bin/ipauth-update.sh
sudo crontab -e →
*/2 * * * * /usr/local/bin/ipauth-update.sh
Click your auth URL, wait 2 min, then check:
sudo ufw status | grep ipauth
For systems without ufw. Older distros, embedded Linux, or anything where you manage iptables directly. Persistence depends on your distro (iptables-persistent, netfilter-persistent, or saved via init).
Save as /usr/local/bin/ipauth-update.sh:
#!/bin/sh
SERVER_URL="https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
PORT=22
COMMENT="ipauth"
CURIP=$(curl --max-time 5 -fsS "$SERVER_URL" | grep -oE '"ipaddress":"[0-9.]+' | cut -d'"' -f4)
[ -z "$CURIP" ] && exit 0
# Remove any prior ipauth allow rules for this port, then add fresh.
iptables -S INPUT | grep -- "--comment \"$COMMENT\"" | sed 's/^-A /-D /' | while read R; do
iptables $R 2>/dev/null
done
iptables -I INPUT -p tcp --dport "$PORT" -s "$CURIP" -m comment --comment "$COMMENT" -j ACCEPT
sudo chmod +x /usr/local/bin/ipauth-update.sh
*/2 * * * * /usr/local/bin/ipauth-update.sh
If you use iptables-persistent, also run netfilter-persistent save after the rule lands, OR add a second cron entry to do it.
sudo iptables -L INPUT -n --line-numbers | grep ipauth
Uses Windows Firewall via PowerShell + Task Scheduler. Run PowerShell as Administrator.
Save as C:\Scripts\ipauth-update.ps1:
# ipauth-update.ps1
$ServerUrl = "https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
$Port = 22
$RuleName = "IPAuth-Allow-$Port"
try {
$resp = Invoke-RestMethod -Uri $ServerUrl -TimeoutSec 5
if (-not $resp.ipaddress) { exit 0 }
$ip = $resp.ipaddress
} catch { exit 0 }
Get-NetFirewallRule -DisplayName $RuleName -ErrorAction SilentlyContinue | Remove-NetFirewallRule
New-NetFirewallRule -DisplayName $RuleName `
-Direction Inbound -Action Allow `
-Protocol TCP -LocalPort $Port `
-RemoteAddress $ip `
-Profile Any | Out-Null
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\ipauth-update.ps1"
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 2) `
-RepetitionDuration (New-TimeSpan -Days 3650)
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "IPAuth-Update" `
-Action $action -Trigger $trigger -Principal $principal `
-Description "Updates Windows Firewall rule from IPAuth"
Get-NetFirewallRule -DisplayName "IPAuth-Allow-*" |
Get-NetFirewallAddressFilter |
Select-Object RemoteAddress
If you're using IPAuth from a Windows laptop (not a server), you probably want it to register your IP every time you open PowerShell. So new SSH sessions to your remote boxes work immediately after a network change. This is what we use ourselves.
Open your $PROFILE:
notepad $PROFILE # (if the file doesn't exist yet, PowerShell will offer to create it)
Add a $Servers hash table and a startup loop that hits each auth URL (not server URL. This is the client-side click equivalent) and prints a status line:
$Servers = @{
'webprod' = @{
IPAuthAuth = 'https://ipauth.net/whitelist/?key=YOUR_AUTH_KEY_HERE'
HealthURL = 'https://web.example.com/'
}
# add more boxes as @{ IPAuthAuth = '...'; HealthURL = '...' }
}
try {
$publicIP = Invoke-RestMethod 'https://api.ipify.org' -TimeoutSec 5
} catch { $publicIP = '(lookup failed)' }
Write-Host "Your public IP is: " -NoNewline
Write-Host $publicIP -ForegroundColor Cyan
$statusParts = @()
foreach ($name in $Servers.Keys | Sort-Object) {
$cfg = $Servers[$name]
try { Invoke-RestMethod $cfg.IPAuthAuth -TimeoutSec 5 | Out-Null; $ipauthOK = $true }
catch { $ipauthOK = $false }
try {
$r = Invoke-WebRequest $cfg.HealthURL -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop
$healthOK = $r.StatusCode -eq 200
} catch { $healthOK = $false }
$color = if ($ipauthOK -and $healthOK) { 'Green' } elseif ($healthOK) { 'Yellow' } else { 'Red' }
$state = if ($healthOK) { 'UP' } else { 'DOWN' }
if (-not $ipauthOK) { $state += '(no-ipauth)' }
$statusParts += @{ Name = $name; State = $state; Color = $color }
}
$first = $true
foreach ($p in $statusParts) {
if (-not $first) { Write-Host " | " -NoNewline }
Write-Host "$($p.Name): " -NoNewline
Write-Host $p.State -ForegroundColor $p.Color -NoNewline
$first = $false
}
Write-Host ""
Save and close. Every new PowerShell window now auto-registers your current IP with every server's IPAuth key in $Servers, prints your public IP, and shows a per-server up/down summary. If you switch networks (coffee shop → home), just open a new PS window and you're back in within the server's poll window.
Server side still needs the cron from steps 1-3 above. This client-side helper just clicks the auth URL for you. The two pieces work together.
macOS ships pf out of the box (BSD heritage). Useful for hardened Macs or a Mac mini acting as a small server. Requires enabling pf and adding an anchor.
Edit /etc/pf.conf (sudo):
anchor "ipauth" load anchor "ipauth" from "/etc/pf.anchors/ipauth"
Create /etc/pf.anchors/ipauth:
table <ipauth_allow> persist pass in proto tcp from <ipauth_allow> to any port 22 keep state
Enable pf and load:
sudo pfctl -e sudo pfctl -f /etc/pf.conf
Save as /usr/local/bin/ipauth-update.sh:
#!/bin/sh
SERVER_URL="https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
CURIP=$(curl --max-time 5 -fsS "$SERVER_URL" | grep -oE '"ipaddress":"[0-9.]+' | cut -d'"' -f4)
[ -z "$CURIP" ] && exit 0
pfctl -a ipauth -t ipauth_allow -T replace "$CURIP" 2>/dev/null
sudo chmod +x /usr/local/bin/ipauth-update.sh
Save as /Library/LaunchDaemons/net.ipauth.update.plist:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>Label</key><string>net.ipauth.update</string>
<key>ProgramArguments</key>
<array><string>/usr/local/bin/ipauth-update.sh</string></array>
<key>StartInterval</key><integer>120</integer>
<key>RunAtLoad</key><true/>
</dict>
</plist>
sudo launchctl load -w /Library/LaunchDaemons/net.ipauth.update.plist
sudo pfctl -a ipauth -t ipauth_allow -T show
Uses a pf table, pfctl updates it in-place, no daemon reload needed. Tested on OpenBSD 7.5+.
Add to /etc/pf.conf:
table <ipauth_allow> persist pass in on egress proto tcp from <ipauth_allow> to any port 22 keep state
doas pfctl -f /etc/pf.conf
#!/bin/sh
SERVER_URL="https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
CURIP=$(ftp -V -M -o - "$SERVER_URL" 2>/dev/null | grep -oE '"ipaddress":"[0-9.]+' | cut -d'"' -f4)
[ -z "$CURIP" ] && exit 0
pfctl -t ipauth_allow -T replace "$CURIP" 2>/dev/null
doas chmod +x /usr/local/bin/ipauth-update.sh
Edit root's crontab via doas crontab -e:
*/2 * * * * /usr/local/bin/ipauth-update.sh
doas pfctl -t ipauth_allow -T show
FreeBSD's native packet filter. Use a single rule that points at a table the script updates in-place. FreeBSD also supports pf if you'd rather. Same flow as the OpenBSD/macOS tabs.
Append to /etc/ipfw.rules (or wherever your ruleset lives):
ipfw table 10 create type addr ipfw add allow tcp from table\(10\) to any 22 in
Reload your ruleset (typically /etc/rc.d/ipfw restart).
#!/bin/sh
SERVER_URL="https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
CURIP=$(fetch -q -T 5 -o - "$SERVER_URL" | grep -oE '"ipaddress":"[0-9.]+' | cut -d'"' -f4)
[ -z "$CURIP" ] && exit 0
# Replace table 10 contents with just the current IP.
ipfw table 10 flush
ipfw table 10 add "$CURIP"
chmod +x /usr/local/bin/ipauth-update.sh
Root's crontab:
*/2 * * * * /usr/local/bin/ipauth-update.sh
ipfw table 10 list
Different from the OS firewall tabs above. This one gates a specific URL path on an existing web server (no kernel firewall changes). Combine HTTP Basic Auth with an IP allowlist managed by IPAuth. Defense in depth: only your current IP can hit the auth challenge, and even if someone learns the IP they still need credentials. Works on Apache 2.4+ wherever AllowOverride permits AuthConfig + Limit.
Put it OUTSIDE the document root.
sudo htpasswd -c /etc/apache2/.htpasswd you # (will prompt for a password) sudo chown root:www-data /etc/apache2/.htpasswd sudo chmod 640 /etc/apache2/.htpasswd
Apache reads .htaccess per-request, so the script can rewrite it any time without a daemon reload. RequireAll means BOTH the password AND the IP must match.
AuthType Basic
AuthName "Restricted"
AuthUserFile /etc/apache2/.htpasswd
# === IPAUTH-IPS-START ===
<RequireAll>
Require valid-user
Require ip 0.0.0.0/32
</RequireAll>
# === IPAUTH-IPS-END ===
The 0.0.0.0/32 placeholder gets replaced on the first script run. Keep the START/END markers exactly as shown. The script uses them as anchors.
Save as /usr/local/bin/ipauth-htaccess.sh. Replace YOUR_SERVER_KEY and the HTACCESS path. State file tracks the last IP so the script only rewrites when it changes (no needless inotify churn or log noise).
#!/bin/sh
SERVER_URL="https://ipauth.net/serverquery/?key=YOUR_SERVER_KEY"
HTACCESS="/var/www/html/protected/.htaccess"
STATE="/var/cache/ipauth-htaccess.last"
CURIP=$(curl --max-time 5 -fsS "$SERVER_URL" | grep -oE '"ipaddress":"[0-9.]+' | cut -d'"' -f4)
[ -z "$CURIP" ] && exit 0
OLDIP=$(cat "$STATE" 2>/dev/null)
[ "$CURIP" = "$OLDIP" ] && exit 0
# Rewrite ONLY the block between markers. Atomic via tmp + mv.
TMP=$(mktemp)
awk -v ip="$CURIP" '
/# === IPAUTH-IPS-START ===/ { print; print "<RequireAll>"; print " Require valid-user"; print " Require ip " ip; print "</RequireAll>"; skip=1; next }
/# === IPAUTH-IPS-END ===/ { skip=0 }
!skip { print }
' "$HTACCESS" > "$TMP" && mv "$TMP" "$HTACCESS"
echo "$CURIP" > "$STATE"
sudo chmod +x /usr/local/bin/ipauth-htaccess.sh
*/2 * * * * /usr/local/bin/ipauth-htaccess.sh
Apache reads .htaccess per request, so no systemctl reload apache2 is needed. The next request after the rewrite sees the new IP.
Click your auth URL, wait 2 min, then check:
grep -A1 IPAUTH /var/www/html/protected/.htaccess
You should see your current IP in the Require ip line.
Want to give someone temporary access? Hand them an .htpasswd credential AND issue them an IPAuth pair via the dashboard. Both must match. Revoke either independently. Pair this with an access group + a tiny script that swaps the Require ip line for a multi-IP list, and you have HTTP Basic Auth that travels with the team.
The flow is the same anywhere: poll the server URL, parse the IP, update your allow list, schedule it every couple of minutes. If you're running on something exotic (nftables on Alpine, OPNsense, pfSense, IPFire, Synology, etc.) the script body changes but the structure doesn't.
Ask us about your setup →