Learn OpenVPN - Client Config Distribution (CCD) & Per-Client Policy
Episode 9 of 23

Learn OpenVPN - Client Config Distribution (CCD) & Per-Client Policy

This episode covers how to give each client different configuration: the client-config-dir directive, the client-connect and client-disconnect scripts as lifecycle hooks, and static IP allocation with ifconfig-push and iroute.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

So far all clients have been treated the same: an address from the same pool, the same routes, the same policy. In the real world that's rarely the case. Managers need access to the entire network, finance admins only to one server, and engineering teams need fixed IP addresses for inbound firewall rules.

Episode 9 covers client config distribution (CCD) — OpenVPN's mechanism for giving each client different configuration. You will learn the client-config-dir directive, the client-connect and client-disconnect scripts as hooks at the start and end of a session, and static IP allocation with ifconfig-push and iroute.

This is the episode that turns OpenVPN from a mere tunnel into an infrastructure with real policy. After this episode, you can grant different levels of access to each user or site.

Client Config Directory

How client-config-dir Works

The client-config-dir directive points to a directory containing per-client configuration files. Each file is named after the client certificate's common name:

Enable the client config directory
client-config-dir /etc/openvpn/server/ccd

When a client connects, OpenVPN looks in that directory for a file with the same name as the client certificate's CN. Example: a client with CN arman will load the file /etc/openvpn/server/ccd/arman. If the file doesn't exist, the client gets the default configuration from server.conf.

Securing the CCD Directory

The CCD directory handles network access control, so make sure only root can write to it:

Secure the CCD directory
mkdir -p /etc/openvpn/server/ccd
chown root:root /etc/openvpn/server/ccd
chmod 700 /etc/openvpn/server/ccd

chmod 700 ensures only the directory owner can access it. A CCD file writable by another user is a security hole: an attacker could inject push directives to hijack other clients' routing.

Contents of a CCD File

A CCD file is a snippet of server configuration loaded specifically for that client. Common directives written here are iroute, ifconfig-push, and push:

CCD file for client site-b
iroute 192.168.10.0 255.255.255.0
ifconfig-push 10.8.0.10 255.255.255.0
push "route 10.20.0.0 255.255.255.0"

The push "route ..." line inside a CCD file is added to the client's configuration, just like a push in server.conf — but now only for that particular client.

Per-Client Static IP Allocation

ifconfig-push for Fixed IPs

When a client needs a fixed IP address inside the VPN subnet, use ifconfig-push in its CCD file. Combined with topology subnet on the server, this gives the client the same static address on every connect:

Static IP allocation in CCD
ifconfig-push 10.8.0.50 255.255.255.0

Addresses allocated with ifconfig-push must lie outside the automatic server pool to avoid conflicts. A good habit: allocate static IPs starting from the lower end of the subnet range and let the dynamic pool fill the upper end.

Avoiding Address Duplication

If you use topology net30 or p2p, the ifconfig-push format is different: it needs two addresses, one for the client and one for the server side. Don't mix formats without understanding the active topology, because a format error produces an option ifconfig-push on non-subnet topology error in the log.

Verifying Allocation

After a client connects, verify in openvpn-status.log that the client received the expected IP. If two clients receive the same IP, hunt down the conflict source in the CCD files — OpenVPN does not automatically reject duplicate addresses.

iroute for Client Subnets

Reaching the Network Behind a Client

iroute tells the server that the subnet behind a client can be reached through that client's tunnel. This is essential for site-to-site and for clients sitting behind NAT:

iroute in a CCD file
iroute 192.168.10.0 255.255.255.0

A common pair of directives for a client representing a site: ifconfig-push gives the client a fixed IP, while iroute gives the server a way to reach the subnet behind the client. Both are usually written side by side in the same CCD file.

Combining with push

For traffic to flow both ways, the server also needs to tell the client the routes to other networks. In the CCD file, add push "route ..." for the networks the client should reach. Without push, the client knows how to send to the server's subnet, but the server must also know how to reach the client's subnet via iroute.

Script Hooks: client-connect and client-disconnect

Running Actions at Session Start and End

OpenVPN calls scripts at certain moments in a client session's lifecycle. The two most useful are client-connect, called after successful authentication before the tunnel is activated, and client-disconnect, called after the session ends:

Declare script hooks on the server
client-connect /etc/openvpn/server/scripts/on-connect.sh
client-disconnect /etc/openvpn/server/scripts/on-disconnect.sh
script-security 2

script-security 2 must be enabled so OpenVPN will run external scripts. Scripts receive client information through environment variables such as common_name, untrusted_ip, and ifconfig_pool_remote_ip.

Example Usage

Script hooks are often used to apply dynamic firewall rules, log access to a database, or send notifications. A simple example that records a new session:

Simple on-connect.sh
#!/bin/bash
echo "$(date) client $common_name connected from $untrusted_ip" >> /var/log/openvpn-ccd.log

After creating the script, don't forget chmod +x so it can be executed. Small mistakes like wrong permissions often make scripts silently fail to run.

Implications for Access Control

Because client-connect is called before the tunnel is active, this script can reject connections with a non-zero exit code. This pattern is used for additional access control beyond certificates — for example rejecting clients not on an allowlist, or enforcing time-based policy.

Conclusion

Key takeaways:

  • client-config-dir maps per-client config files based on the certificate CN.
  • ifconfig-push gives a client a fixed IP address in the VPN subnet.
  • iroute tells the server how to reach a subnet behind a client.
  • push "route ..." inside a CCD file is specific to that client.
  • client-connect and client-disconnect are session lifecycle hooks.
  • Secure the CCD directory with strict permissions because it controls network access.

In the next episode, episode 10, we will discuss advanced authentication — LDAP and RADIUS integration to replace static user files, the auth-pam and auth-radius plugins, and adding MFA with OTP and TOTP as a second security layer. After this episode, your OpenVPN authentication is ready for enterprise scale.