Denis Arnst

Tunneling traffic just for one Linux system user through a VPN


One Open-Source software to easily to the job:

It can be even used with systemd (source):


ExecStart=/usr/local/sbin/namespaced-openvpn --namespace %i --daemon nsovpn-%i --status /run/namespaced-openvpn/%i.status 10 --cd /etc/openvpn --script-security 2 --config /etc/openvpn/%i.conf --auth-user-pass /etc/openvpn/auth.txt  --writepid /run/namespaced-openvpn/
ExecReload=/bin/kill -HUP $MAINPID



i.e. systemctl start nsopenvpn@configfile
Another systemd service can then easily use it by using NetworkNamespacePath=configname inside the [Service] section.
Also with Require=nsopenvpn@configfile it can be assured that the VPN is started before.

It will create for the process a sandboxed network environment. It is quite secure (of IP leaks) however it is also the most restrictive way. It is not possible to access local network services. To do so one would have to either add additional (possible complicated) routes/bridges or create a socat device.

A system user using a shell is also not automatic protected this way.


If security is not that important, one can use iptables to do a similar job.

With that local communication is still possible. Also it is easy to really put the whole user inside a VPN (without requiring a special command for starting a shell inside the VPN).


# Mark all traffic with the number 42 of a specific user when its not towards
iptables -t mangle -nvL OUTPUT | grep -q 0x2a ||
    iptables -t mangle -A OUTPUT ! -d -m owner --uid-owner THEUSERTORESTRICT -j MARK --set-mark 42

# Create a new routing table if not exists
grep -q '^42  vpn$' /etc/iproute2/rt_tables ||
    echo '42  vpn' >>/etc/iproute2/rt_tables

# Create a gateway with low priority which routes nowhere (to disconnect when the VPN interface is removed)
ip route add default via metric 10000 table vpn || true

# Get the subnet of tun0 Interface, and replace the last octet with a .1 as this is commonly the gateway. Might need to be replaced
subnet=`ip -o -f inet addr show tun0 | awk '/scope global/ {print $4}' | perl -ne 's/(?<=\d.)\d{1,3}(?=\/)/0/g; print;'`

# Add the VPN as gateway for traffic in the routing table
ip route show table vpn | grep -q "metric 11" ||
    ip route add default via $gatewayip dev tun0 metric 11 table vpn

# ip route show table vpn

# All traffic marked with the number 42 should go through the VPN routing table
ip rule | grep -q 0x2a ||
    ip rule add fwmark 42 lookup vpn prio 42

# Masquerade/SNAT on the VPN interface
iptables -t nat -nvL POSTROUTING | grep -q tun0 ||
    iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

The script was written with the help of:

It is not 100% optimal. It guesses the gateway IP because OpenVPN does not transmit the Gateway IP into ENV when using the OpenVPN option pull-filter ignore redirect-gateway

Also a disconnect of tun0 is protected in a very primitive way.

I use it like this inside a custom OpenVPN systemd script (having the code from above in a file called; also don’t forget to option from above inside the vpns configurationfile):

/usr/sbin/openvpn --script-security 2 --config /etc/openvpn/myvpn.conf --auth-user-pass /etc/openvpn/auth.txt  --writepid /run/namespaced-openvpn/ --route-up /etc/openvpn/

It is easily testable by suing into the user and then running curl

Installing Pterodactyl for Plesk

Pterodactyl is a software for managing game servers. As it’s still in development, installing it in some environments is not straight-forward. You should read the official docs along. The guide is written for Plesk with CentOS but could be helpful for other panel users like cPanel too.

Originally published 9. Sep 2017
EDIT (7. Feb 2018): Fixed error thanks to samyratchet
EDIT (15. Mar 2018): Updated for Pterodactyl v0.7.6
EDIT (28. Jun 2018): Updated for Pterodactyl v0.7.7
EDIT (06. Mar 2019): Updated for Pterodactyl v0.7.13
EDIT (11. May 2020): Updated for Pterodactyl v0.7.17; Added screenshots
EDIT (6. Dec 2020): Updated for Pterodactyl v1.1.1; Use –user SystemD


Fixing Let’s Encrypt with custom Nginx rules on Plesk

When having expressions like this:

location ~ ^/(?:\.|include) {
    deny all;

All requests which start with a dot (.) or include get denied. As such the required .well-known folder used for the acme challenge by Let’s Encrypt gets denied too and Plesk is unable to renew certificates.

To solve it, it is possible to add (?!well-known) to every regex which is in conflict. However, it is easier to simply define a new location block on the very top of the configuration:

location ^~ /.well-known/acme-challenge/ {
    default_type “text/plain”;