Limit your sites to VPN and local clients only

The problem: You have a bunch of sites running behind an nginx proxy, and you want to limit access to some of the more critical ones to your VPN clients only as a second security layer. You also have an ISP that will sometimes change your public IP address, so you are using the services of a dynamic DNS provider such as DuckDNS.

The solution: YMMV, but generally, web requests from your VPN clients will originate from the public IP of your VPN server. Well that’s handy – you only have to tell nginx to allow anything from this IP, right?

Not so fast: nginx has no idea of its own public IP address. There are nginx modules that will do a reverse DNS lookup on a hostname (like the one provided by your dynamic DNS provider), but as this has to happen on every request, the performance hit will be substantial.

However, you already have a cron job running every once in a while informing DuckDNS of your current IP address – you simply have to add a couple of lines to your script so that it informs nginx, too! Here’s an example of that addition:

resolved_ip=$(dig a +short);
if [ -n "$resolved_ip" ]; then
    echo "allow $resolved_ip;" > /path/to/duck.conf;

Test your script – duck.conf should now contain a single allow directive with your public IP. You can safely include it in the configuration file for the site you’d like to limit – under the server, location or http block:

include /path/to/duck.conf;
deny all;

Restart nginx and test accessing with and without VPN.

edit: It hadn’t occurred to me originally, but this will also cover any clients from your local network too (since they are also behind the same public IP) – unless they access the sites via IP instead of domain name. This may or may not be an added benefit.
edit 2: Encased that echo in an if block because saving an empty $resolved_ip (say, if your Internet connection is temporary down) will bork your whole nginx on the next restart.