obfs4proxy-openvpn: Obfuscating OpenVPN traffic using obfs4proxy

This post provides a more in-depth look at the obfs4proxy-openvpn script. You don’t need to fully read it to make use of the script, but it will help you to get the most out of it.


After my initial post about obfs4 on how to hide any TCP traffic and an example for hiding SSH traffic, it’s now time to do so for OpenVPN.

For this, I have written a Bash script to do the job. It’s called obfs4proxy-openvpn and is freely available under MIT license.

Supported transports


The main goal of the script is to provide obfs4 transport to OpenVPN. This is also the main interest of this article.

This transport requires out-of-band CERT exchange between client and server and because of that, can provide some advanced functionalities which are missing in older transports.


obfs3 transport is supported but should generally be avoided in favor of obfs4.


obfs2, the oldest transport is supported as well (mainly because its supported by obfs4proxy). You really shouldn’t use it…


Before going into detail, its good to have a basic idea on how different parts of the script work together to provide obfs4 functionality to openvpn.

Client architecture

obfs4proxy-openvpn client diagram.png

  1. The script reads and validates its config file. This file is responsible for putting it in client mode as well as passing the required settings to it.
  2. The script does a basic check and validation on the specified openvpn config file. This file will be passed to the openvpn process later on.
  3. At this stage, obfs4proxy process is started acting as a SOCKS5 proxy server, listening to a random port.
  4. Based on its config, the script generates an appropriate socks5_auth file compatible with the openvpn.
  5. Using the specified openvpn config file, obfs4proxy local SOCKS5 port, and socks5_auth file, the execution is now passed to the openvpn client.
  6. openvpn client tries to connect to the remote server using the local obfs4proxy SOCKS5 proxy.
  7. obfs4proxy obfuscates the traffic and sends them along the line.

Server architecture

obfs4proxy-openvpn server diagram.png

  1. The script reads and validates its config file. This file is responsible for putting it in server mode as well as passing the required settings to it.
  2. The script does a basic check and validation on the specified openvpn config file. This file will be passed to the openvpn process later on.
  3. At this stage, obfs4proxy process is started. It binds to a valid IP address and port.
  4. Based on its config, the script starts openvpn server process, passing it the required port/address for binding (to receive traffic from obfs4proxy).
  5. obfs4proxy receives the obfuscated traffic from the internet and de-obfuscate it.
  6. The de-obfuscated traffic is passed to the openvpn server.

obfs4proxy-openvpn config

A sample config file is provided in the examples/ folder.

Please also note that this file (specially when used in client mode), will contain possibly sensitive data (i.e, the CERT). So better to set its permission to either 600 or 640:

chmod 600 /etc/obfs4proxy-openvpn.conf

Below are all the valid options along with a description for each (default values are placed inside parenthesis):

General options

These are options that are valid for both client and server:

  • MODE: mode of operation client/server.

  • TRANSPORT (obfs4): obfs4,obfs3 and obfs2 is supported.

  • OPENVPN_CONFIG_FILE: For maximum flexibility, the script does not magically generate an openvpn config file for you, and expects that you provide it with one instead. There are however almost ready-to-use openvpn config samples available in the repository which you may use.

  • OBFS4PROXY_WORKING_DIR (/var/lib/obfs4proxy-openvpn): The directory path that will be used for the obfsproxy log, config, as well as the socks5_auth file (in client mode). If the path does not exist, it will be automatically created by the script with the required permissions. It is possible to run multiple instance of the script by using different paths and config files.

  • OBFS4PROXY_UID / OBFS4PROXY_GID (obfs4-ovpn / obfs4-ovpn): The user and group name that obfs4proxy will run in. If neither UID nor GID already exist on the system (and their value matches), they will be automatically created by the script1. The home directory of the UID, will be set to OBFS4PROXY_WORKING_DIR. So I suggest that you use different set of UID/GID per each script instance2.

  • OBFS4PROXY_LOG_LEVEL (error): Can be one of none,error,warn,info,debug values which indicates the level of log for obfs4proxy.

  • OBFS4PROXY_LOG_IP (false): Should supposedly disable scrubbing addresses in the obfs4proxy log if its set to true3.

obfs4 specific options

These options are valid only when using obfs4 transport:

  • IAT_MODE (0): Inter-Arrival Time mode. Essentially this option provides some protections against packet timing fingerprints. Valid values are 0, 1 or 2. Client and server values don’t need to be matched. Do note that you would sacrifice some performance if enabled (specially if it’s set to 2).

Client options

These options are only valid when running in client mode and are ignored in server mode:

  • CLIENT_UPSTREAM_PROXY: The optional upstream proxy that obfs4proxy should use when making outgoing network connections. It is a URI [RFC3986] of the format:

obfs4 specific client options

These options are only valid when the client using obfs4 transport:

  • CLIENT_REMOTE_CERT: This is a 70-characters long base64 string which must be imported from the obfs4proxy running on the server side. It’s basically a combination of CLIENT_REMOTE_NODE_ID and CLIENT_REMOTE_PUBLIC_KEY for maximum portability.

  • CLIENT_REMOTE_NODE_ID / CLIENT_REMOTE_PUBLIC_KEY: At its core, obfs4proxy uses these values for obfs4 transport. They can be directly specified instead of CLIENT_REMOTE_CERT. But you generally shouldn’t be needing to do that.

Server options

These options are only valid when running in server mode and are ignored in client mode:

  • SERVER_OBFS4_BIND_ADDR / SERVER_OBFS4_BIND_PORT ( / 1516): This is the address:port that the obfs4proxy server would listen for incoming connections from obfs4proxy client (i.e, It is the same address:port that you should put in the --remote host [port] directive in openvpn client config file). Note that you may run into permission issues if you try to specify a port less than 10244.

  • SERVER_OPENVPN_BIND_ADDR / SERVER_OPENVPN_BIND_ADDR ( / 1515): This is the address:port of the openvpn server that obfs4proxy server would redirect de-obfuscated traffic to. For this reason, the address is usually to ensure real openvpn port is not accessible to the public. Another valid use is when the address is set to making openvpn to bind to all interfaces. This could be beneficial and provides both obfuscated and non-obfuscated openvpn traffic at the same time.

OpenVPN configs

The script requires you to specify an openvpn config file. This is your typical openvpn client/server conf file in most aspects with couple of restrictions:

  • Only TCP tunneling is supported and no UDP specific option should be specified. You don’t need to explicitly set the --proto, this will be done by the script.
  • Don’t use --daemon or --inetd. You may however run obfs4proxy-openvpn as a service.
  • You may not use any related proxy options in client mode (e.g, --http-proxy, --socks-proxy). If you need to use a proxy to connect to the internet, it should be specified in CLIENT_UPSTREAM_PROXY.
  • You may not use --local,--port or --lport in server mode. These settings are adjusted via SERVER_OPENVPN_BIND_ADDR / SERVER_OPENVPN_BIND_ADDR.


You should consider running openvpn process in a limited (and preferably dedicated) account. The nobody UID should be available in most distros and it’s easy to use (but again, a better approach would be having a dedicated account for it).

This is more of an OpenVPN topic but it usually involves adding --user and --group directives in the openvpn config file along with --persist-tun and --persist-key to deal with any permission issues that might arise upon receiving SIGUSR1.

In client mode when using obfs4 transport, openvpn process also needs to access the socks5_auth file. For that reason, it is recommended that t uses the same group as obfs4proxy. This is not needed in server mode.

To recap, if you plan to run openvpn in a limited account (e.g, “nobody”), these would be the appropriate directives in the openvpn conf file:

In client mode:

user nobody
group obfs4-ovpn

In server mode:

user nobody

Hardcoded arguments

To make openvpn work with obfs4proxy, these hardcoded arguments are passed to it:

In client mode:

openvpn \
  --proto tcp-client \
  --socks-proxy $CLIENT_OBFS4_SOCKS5_PORT $OBFS4PROXY_WORKING_DIR/socks5_auth

Note that CLIENT_OBFS4_SOCKS5_PORT will be generated by the obfs4proxy on each run and is not adjustable. $OBFS4PROXY_WORKING_DIR/socks5_auth is also only passed to the openvpn process when using obfs4 transport.

In server mode:

openvpn \
--proto tcp-server \

CIA triad

This section applies to obfs4 transport ONLY. In particular, it does NOT apply to obfs3/obfs2 transports.
DISCLAIMER: I’m not a cryptography expert. Do your own research first and do not blindly accept what you read on the internet.

Performance can be slightly improved by getting advantage of the obfs4 protocol encryption.

obfs4-spec suggests that the CIA triad concept applies obfs4 protocol as well. So If the CERT kept secret, one may rely on obfs4 alone for confidentiality, integrity and authenticity. In such cases, it might be okay to disable openvpn’s own encryption and authentication mechanisms (You do this by not including any --secret or tls related settings.

If the CERT is made publicly available, confidentiality and integrity are still kept but the client authenticity is lost. You have to rely on other means (like pre-shared secret keys or tls auth) to authenticate the clients. But it still might be okay to use --cipher none with it.

In both cases, openvpn should output a scary warning message about the situation.

If you have even the smallest doubt, just play it safe. It takes less than a second to setup a simple pre-shared key on the server:
openvpn --genkey --secret /etc/openvpn/secret.obfs4.key

and you’re not really reading this article to optimize OpenVPN throughput now, are you?!

obfs4proxy-openvpn arguments

The script comes with a limited support for command line arguments:

  • -h, --help: Outputs a simple help on the supported command line arguments.
  • -c, --config CONFIG_FILE: Points to obfs4proxy-openvpn config file that the script should employ. Default is /etc/obfs4proxy-openvpn.conf. One use for this is to run multiple instances of the script simultaneously. It’s also possible to use CONFIG_FILE alone as the only non-optional parameter.
  • --export-cert FILE: This option is only valid in server mode. The mandatory FILE argument is the path of a non-existent file to save the CERT string into (use - for stdout).
  • -V, --version: Outputs version number and copyright information.

Running as a service

After the execution is passed to it, openvpn process stays in the foreground until it ended (at that point the script will cleanup after itself and exit).

It’s possible to run the script as a service. A sample service file is provided in the examples/ folder. Download it and follow these simple steps to setup the service:

  1. Adjust the ExecStart line if required
  2. Move and rename to the correct location:
    mv examples/obfs4proxy-openvpn.service.sample /etc/systemd/system/obfs4proxy-openvpn.service
  3. Reload systemd configuration:
    systemctl daemon-reload
  4. Start the service:
    systemctl start obfs4proxy-openvpn.service
  5. Optionally check its status:
    systemctl status obfs4proxy-openvpn.service
  6. Enable the service at startup:
    systemctl enable obfs4proxy-openvpn.service

  1. Using adduser for Debian based distros and useradd for the rest. ↩︎

  2. You’d be able to delete the associated data just by deleting the user then. ↩︎

  3. If you could make that actually work, let me know! ↩︎

  4. An acceptable workaround for this is provided in my another post: How to hide (obfuscate) SSH traffic using obfs4 ↩︎

a sysadmin in the wind
comments powered by Disqus