Circumventing Network Bans with WireGuard
Before this week, it had been a long time since I visited the Plex subreddit.
I shared my last article there, which was a technical write-up of moving my Plex instance from a Hetzner auction server to a virtual machine running on hardware in my home network, and the considerations that influenced the migration.
It didn’t take long for me to realize that a culture of hostility towards even the mention of Hetzner or other cloud hosting providers has strongly taken root since Plex announced it’s blanket network ban on IP ranges associated with Hetzner data centers.
I saw many posts and comments of users asking about issues with their Plex instances that had for years been working without issue on Hetzner servers until this past October when Plex enacted their very poorly communicated network ban, which hit a significant number of customers like myself who had paid for a lifetime Plex Pass.
Although I myself am not pursuing this option for reasons outlined in my last article, I wanted to share a clear and detailed example of how to circumvent Plex’s ban on IPs originating from Hetzner data centers (because gatekeeping is for losers).
WireGuard VPN Connection Details⌗
This can work with any WireGuard VPN provider (even with your own WireGuard server on another machine!) but for the sake of simplicity I have chosen to use Mullvad as the reference in this tutorial.
- Go to mullvad.net and open an account
- This is actually a very cool process; no email, no details, they just provide you a secret account ID
- Once you have an account, navigate to “Add time to your
account”
- You can just add 1 month of time for 5 EUR if you want to try out the quality of their service
- Head over to the WireGuard Configuration page
- Select “Linux” and then hit “Generate key”
- Select a Country, City and Server for your exit location (bottom of the page)
- Scroll down a little more to hit “Download file” and get your authentication details
- You’ll only be able to do this once! Make sure you do it before you navigate away
At this point you’ll have a .conf
file containing the fields
Interface.PrivateKey
and Interface.Address
which you’ll need later.
Server Configuration⌗
Below is a fully annotated NixOS server configuration which sets some sane server defaults, configures SSH access, firewall rules, and brings up a Plex container which sends all outgoing requests through a WireGuard VPN using your new connection details.
This server configuration does not include hardware configuration, which is
naturally prone to variation, especially on auction servers, however it should
not be too difficult to adapt my
nixos-hetzner-robot-starter
template (video walkthrough) to
work with your server’s hardware.
{
config,
}: let
# These are helper functions to look up uids and gids
# which take a single string argument
#
# eg. uid "samira" -> returns the uid for the "samira" user
uid = username:
if config.users.users.${username}.uid == null
then "1000"
else toString config.users.users.${username}.uid;
# eg. gid "users" -> returns the gid for the "users" group
gid = group:
if config.users.groups.${group}.gid == null
then "100"
else toString config.users.groups.${group}.gid;
# FIXME: Set your username for this server
username = "<YOUR PREFERRED USERNAME>";
# FIXME: Set this or you won't be able to SSH
publicKeys = [
"<YOUR PUBLIC KEY>"
"<OPTIONALLY ANY OTHER PUBLIC KEYS OF YOURS>"
];
in {
# This stops Docker interference with dhcp
networking.dhcpcd.denyInterfaces = ["veth*"];
# This allows incoming connections on port 32400 for Plex
networking.firewall.allowedTCPPorts = [32400];
# This sets the hostname of the server, this can be anything you like
networking.hostName = "plex-on-hetz";
# This is so that users in the "wheel" group don't need to
# enter their password for sudo commands
security.sudo.wheelNeedsPassword = false;
# This enables SSH access to the server and only allows
# root SSH connections with SSH keys, never with passwords
services.openssh = {
enable = true;
settings.PermitRootLogin = "prohibit-password";
};
# This sets the SSH public key(s) you can use to connect
# to the server with the "root" user
users.users.root.openssh.authorizedKeys.keys = publicKeys;
# This creates your user on the server
users.users.${username} = {
# This specifies that you are a user that gets home directory
isNormalUser = true;
# This adds your user to the "wheel" and "docker" groups
# In the "wheel" group, you don't need to use your password for sudo
# In the "docker" group, you don't need to use sudo for docker commands
extraGroups = ["wheel" "docker"];
# This sets the SSH public key(s) you can use to connect
# to the server with your user
openssh.authorizedKeys.keys = publicKeys;
};
# This is a service that bans hosts and IPs that produce authentication
# errors when trying to SSH multiple times in quick succession
services.fail2ban = {
enable = true;
# FIXME: Add this so you don't get locked out by mistake
ignoreIP = ["<YOUR HOME IP ADDRESS>"];
};
# This enables Docker, makes sure it runs when the server starts
# and automatically prunes dangling resources to keep space free
virtualisation.docker = {
enable = true;
enableOnBoot = true;
autoPrune.enable = true;
};
# This is where you can add Docker containers, you can think of this
# block as being conceptually similar to docker-compose in some ways
virtualisation.oci-containers = {
backend = "docker";
# This is the gluetun container: https://github.com/qdm12/gluetun
# gluetun is a thin docker container for multiple VPN providers that
# supports WireGuard
containers.gluetun = {
# This is so that the container starts automatically after server reboots
autoStart = true;
image = "qmcgaw/gluetun:v3.37.0";
# This is so that we can access Plex on http://localhost:32400
# from the server, ie. outside of the container
ports = [
"32400:32400"
];
# This is where you enter authentication information you get from Mullvad
environment = {
VPN_SERVICE_PROVIDER = "mullvad";
VPN_TYPE = "wireguard";
# FIXME: Don't forget to add your connection details here!
WIREGUARD_PRIVATE_KEY = "<Interface.PrivateKey in your downloaded conf file>";
WIREGUARD_ADDRESSES = "<Interface.Address in your downloaded conf file>";
};
# This capability is required by gluetun
extraOptions = [
"--cap-add=NET_ADMIN"
];
};
# This is the Plex container
containers.plex = {
# This is so that the container starts automatically after server reboots
autoStart = true;
image = "plexinc/pms-docker:1.32.8.7639-fb6452ebf";
# This is so that the `plex` user inside of the container has the same
# UID and GID as your user to avoid permissions issues with any directories
# that are mounted
environment = {
PLEX_UID = uid username;
PLEX_GID = gid "users";
};
volumes = [
# This is so that the configuration can be persisted outside of the container
# in the ${HOME}/plex directory on the server
#
# NOTE: If you are migrating an instance started with `services.plex.enable` you
# will need to set this as
# "/path/to/your/plex-nixos/config/dir:/config/Library/Application\ Support"
"/home/${username}/plex:/config"
# This is where you share your media files that are under your user account
# on the server with the Plex container so they can be seen, indexed and played
#
# NOTE: If you are migrating an instance started with `services.plex.enable` you
# will need to make sure the paths inside the container (on the right hand side of the :)
# match the paths that your Plex instance running as a NixOS service reference
#
# TODO: Whatever makes sense for you
"/home/${username}/path/to/tv:/data/tv"
"/home/${username}/path/to/movies:/data/movies"
"/home/${username}/path/to/music:/data/music"
];
# This is to make sure that this container won't start until the `gluetun` container
# has started and is healthy
dependsOn = ["gluetun"];
# This is to make sure that network requests from this container go through the Mullvad
# WireGuard VPN running in the `gluetun` container
#
# This is the key part that allows us to circumvent Plex's Hetzner network ban, because
# it ensures that requests to https://*.plex.tv endpoints look like they are coming
# from whichever WireGuard server we are connected to in the `gluetun` container
#
# This setup also ensures that other processes running on the server can continue
# sending outgoing HTTP requests normally without going through the VPN
extraOptions = [
"--network=container:gluetun"
];
};
};
}
If you have any questions or comments you can reach out to me on Twitter and Mastodon.
If you’re interested in what I read to come up with solutions like this one, you can subscribe to my Software Development RSS feed.
If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.
If you found this content valuable, or if you are a happy user of
komorebi
or my NixOS starter
templates, please consider sponsoring me on
GitHub or tipping me on
Ko-fi.
Mullvad and Hetzner: Please feel free to give me some free VPN time / compute power for all this free positive PR ;)