How can I ensure openVPN clients persistently take the same VPN IP address?

I have added client-config-dir ccd to openvpn.conf and created files in /etc/openvpn/ccd/ with the names of the clients eg. "/etc/openvpn/ccd/CLIENTNAME" simply contains but this IP address is not assigned to a client connecting with CLIENTNAME.ovpn

For context, the point of the VPN is to allow several remote clients to access each other from arbitrary locations, but they should use the default local routing for everything else: www PoE cameras etc. OpenVPN Server is running inside a docker using https://github.com/kylemanna/docker-openvpn

So client certificate/ovpns were generated with: docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm -it kylemanna/openvpn easyrsa build-client-full CLIENTNAME nopass docker run -v $OVPN_DATA:/etc/openvpn --log-driver=none --rm kylemanna/openvpn ovpn_getclient CLIENTNAME > CLIENTNAME.ovpn

client is OpenVPN 2.4.7 on Ubuntu 16.04

my local .ovpn config

dev tun
remote-cert-tls server

remote XXX.XXX.XXX.XXX 1194 udp

# only route the subnet

# various certificates / keys

My server openvpn.conf

# client specific configurations
client-config-dir ccd

# allow clients to reach other

verb 3
key /etc/openvpn/pki/private/XXX.XXX.XXX.XXX.key
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/XXX.XXX.XXX.XXX.crt
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/pki/ta.key
key-direction 0
keepalive 10 60

proto udp
# Rely on Docker to do port mapping, internally always 1194
port 1194
dev tun0
status /tmp/openvpn-status.log

user nobody
group nogroup
comp-lzo no

### Route Configurations Below

### Push Configurations Below
push "block-outside-dns"
push "dhcp-option DNS"
push "dhcp-option DNS"
push "comp-lzo no"

Read man openvpn.

Client config dir files don't work that way. It should contain the same OpenVPN options as you insert into main config file. When that client connects, those options execute with the context set to that client.

To set up client IP address with CCD file, you insert ifconfig-push command into its CCD file:


I changed address slightly because it seems you are using a net30 topology and thus clients are expected to have 4n+2 addresses while their local OpenVPN-internal address is 4n+1.

Simpler way is simply using an ifconfig-pool-persist option, where you specify file to store cname-ip associations. This file is read on server starup. If connecting client still doesn't have an association, its address is allocated and updated table kept in memory. It is written to disk when server shuts down. (I depicted this for you to not try to modify this file while server is running — it will be overwritten.) This way you don't need to bother with creating CCD files. But, there could be occasions where client gets another address. These cases are: too many clients (more than there are addresses in the pool) or duplicate-cn option enabled.

The third way is to use client-connect script, which can generate a part of a configuration on the fly. It is like CCD file, except that the file is generated in temporary place by the script. This is most flexible way, but it requires much more effort. I once used this to automatically add DNS record on client connect.

OpenVPN uses these three sources of information about client addresses in the following order: client-connect script, then CCD file, then IPP file.

The random notes.

Your configuration is suboptimal, as expected from autogenerated configuration. For example, you instruct server to push routes, but set clients to reject routes pushed from server. Better is not to reject routes, but to remove push instruction. Then, if you decide to push some route, you'll need then to only modify server config, but not clietns. You will even be able to set up routing per client using CCD file or client-connect script.

You rewrite DNS configuration on the client. When client disconnects, nobody guarantees they revert configuration back to what it was before connection. More, think about client with two different OpenVPN each modifying DNS. That'll be the hell. This and some other reasoning lead to the lesson here: NEVER push any DNS options to VPN clients unless you absolutely have to! (This is general statement, not just for OpenVPN.)

You disable LZO compression. Why? Network is slower than a slowest CPU, it will be always the bottleneck, so I can't imagine a case where you wouldn't benefit from compression.

You dump server status to a file. While this is not bad idea, OpenVPN could do better. There is management command, which opens a port (I use 7505) and you can "telnet" to it. There you query the status (see the same data it dumps into file), but you can select a machine-parseable format for easy automation. You can kill a client. You can debug OpenVPN by running realtime log. You can change log verbosity on the fly. This is the Good Thing overall. However, I don't know how this would work with Docker.

I'll tell again, read man openvpn. There are countless possibilities, and it is good idea to read it at least to know what it could do for you. OpenVPN is very capable software.


The syntax of your CCD files are incorrect.

Look at ifconfig-push directive.eg

  ifconfig-push IP.add.RR.ESS netmask

Depending on your goal there is an alternative format ifconfig-push IP.add.r1 IP.addr.2 where both IPs are in the same /30, and are not gateway or broadcast, eg and but not or

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.