Configuring OpenVPN in an LXC container
Running OpenVPN inside an LXC container is a practical solution when you need an isolated VPN service without the overhead of a full separate virtual machine. This approach makes it possible to reduce resource usage, keep the VPN role separate from other Linux workloads, and still preserve clean administration. It is especially useful when one host system runs several services and the administrator wants a dedicated, controlled, and easily maintainable VPN environment with its own network logic, firewall rules, and access model.
In practice, OpenVPN in LXC is commonly used for secure remote access to company resources, internal servers, file storage, administrative tools, or private web applications. This model is often deployed on Virtual Servers, because they provide full control over the Linux host, networking, and container configuration. When the workload grows or the number of VPN users increases, the same design is often moved onto Dedicated Servers. In more advanced environments where VPN must be combined with routing, remote workplaces, or custom access policies, the broader design is often delivered as Individual Solutions.
Before configuration begins, it is important to understand one key difference: OpenVPN inside an LXC container is not exactly the same as OpenVPN inside a full virtual machine. An LXC container does not have complete kernel independence, so it must be granted access to the required kernel-level device interfaces, especially the TUN device. If that is not done, OpenVPN may install correctly but still fail to create the tun interface and therefore fail to function as a real VPN service. Many practical problems in this topic are caused not by OpenVPN package installation itself, but by the isolation boundaries between the container and the host.
The usual workflow is straightforward. First prepare the LXC container itself. Then make sure the container is allowed to use TUN or TAP as needed. After that install OpenVPN and the helper tools, prepare certificates or keys, define the server configuration, enable IP forwarding, and set up NAT or routing. Only then should client access be tested. When this order is respected, the deployment is much easier to debug and far more predictable.
The first technical task is to confirm that the container can access the TUN device from the host side. Depending on the LXC or Proxmox setup, this often means allowing the relevant device in container configuration and adding the necessary cgroup and mount rules. This step is often the decisive factor in whether OpenVPN will start successfully at all.
# Example LXC configuration lines lxc.cgroup.devices.allow = c 10:200 rwm lxc.mount.entry = /dev/net/tun dev/net/tun none bind,create=file
Once that is prepared, the OpenVPN packages can be installed inside the container. In many Debian- or Ubuntu-based LXC environments the standard package manager is enough. It is also useful to install easy-rsa if you plan to generate certificates locally. If your certificates are prepared elsewhere, the process is simpler because only the final server-side files need to be copied into the container.
apt update apt install -y openvpn easy-rsa iproute2 iptables
The next major step is certificate and key preparation. OpenVPN commonly uses a TLS model with a certificate authority, a server certificate, a server private key, and individual client certificates. For small and medium environments this is still a strong and transparent design. If certificates are created outside the container, they should be copied in securely and stored with correct file permissions. Private keys should never be placed in world-readable locations or moved through careless ad hoc channels.
# Example file layout /etc/openvpn/server/ca.crt /etc/openvpn/server/server.crt /etc/openvpn/server/server.key /etc/openvpn/server/dh.pem /etc/openvpn/server/ta.key
The server configuration file is where the core OpenVPN behavior is defined: the port, protocol, device type, certificate paths, VPN subnet, pushed DNS values, and routing model. In a small deployment it is common to use UDP and a dedicated private subnet such as 10.8.0.0/24. It is wise to choose a network that does not clash with the private home or office networks your users already have, because overlapping networks create frustrating routing problems later.
# Example /etc/openvpn/server/server.conf port 1194 proto udp dev tun ca /etc/openvpn/server/ca.crt cert /etc/openvpn/server/server.crt key /etc/openvpn/server/server.key dh /etc/openvpn/server/dh.pem tls-auth /etc/openvpn/server/ta.key 0 server 10.8.0.0 255.255.255.0 ifconfig-pool-persist /var/log/openvpn/ipp.txt push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 1.1.1.1" keepalive 10 120 user nobody group nogroup persist-key persist-tun cipher AES-256-GCM auth SHA256 verb 3
However, the OpenVPN configuration file alone is not enough. The VPN server usually needs to route traffic either to the internet or to internal company resources. That requires IP forwarding and either NAT or proper route propagation. If this step is skipped, the client may connect successfully but still be unable to reach anything useful. That is why testing should never stop at “the tunnel is up”. You also need to confirm that traffic actually flows to the intended destinations.
# Enable IP forwarding echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf sysctl -p # Example NAT rule iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
In LXC environments, part of the network behavior may also depend on the host itself, not only on what happens inside the container. If the host has firewall filtering, bridge restrictions, or other traffic policies, OpenVPN traffic can still be affected even when the server.conf inside the container looks correct. That means a successful design should always be treated as a combined host-and-container project rather than as a single application config file.
After configuration is complete, the service can be started and logs should be reviewed immediately. OpenVPN is usually quite clear when it cannot access the TUN device, cannot read keys, lacks permissions, or fails in routing logic. This is exactly why systemd status output and service logs should be part of the normal workflow. Many deployment issues are solved much faster by reading logs than by repeatedly changing random lines in the configuration.
systemctl enable openvpn-server@server systemctl start openvpn-server@server systemctl status openvpn-server@server journalctl -u openvpn-server@server -n 50
Security, stability, and the most common mistakes
From a security standpoint, OpenVPN inside an LXC container should be treated like any other internet-facing service. Access should be limited to the required port only, packages should be kept updated, certificate material should be stored carefully, and there should be a defined process for revoking client credentials. If several users share the same client profile or private key, the environment becomes harder to manage and significantly weaker from a security perspective. It is much better to issue a separate client profile per user or per device.
From a stability perspective, the most common problems are missing TUN access, forgotten IP forwarding, incorrect NAT rules, overlapping client networks, and host firewall rules that block traffic. Another common mistake is assuming that if the OpenVPN process starts, everything is already correct. The real test is whether the client receives an address, reaches the required internal resources, resolves DNS properly, and routes traffic as intended.
Good practice is to document the VPN subnet, server port, protocol, certificate locations, NAT rules, and the host-side LXC settings that make TUN access possible. The host-side piece is often forgotten, and several months later nobody remembers why that container can access /dev/net/tun at all. That kind of documentation dramatically speeds up troubleshooting and makes migration or recovery much easier.
Overall, OpenVPN inside an LXC container is a very useful design when you need a lightweight, isolated, and manageable VPN service in a Linux environment. It offers flexibility and efficient resource usage, but it also requires a careful understanding of networking, kernel device access, certificates, and host configuration. When those parts are handled correctly, the result is a stable and secure VPN environment that remains clear and maintainable over the long term.