A dashboard asks for a connected-for number and OpenVPN does not provide one. The client knows when its tunnel came up, but only if something records that moment before the session state is lost. The supported place is the up hook, and the value worth storing is a raw epoch second rather than a formatted string. Everything after that is arithmetic, plus a few ordering rules that decide whether the number stays honest.

Confirm the hooks actually run

Prove the hook fires before designing anything around it. A minimal client configuration needs script-security 2 plus explicit up and down paths; without the security line, OpenVPN refuses to execute the script and says so plainly. Add up-restart and down-pre at the same time so the restart and teardown paths behave predictably.

Make the first version of the hook append its environment to a file and do nothing else. That tells you the variable names your build actually exports, such as dev and script_type, instead of the ones you remember from another version. Run one connect and one disconnect, then read the file. If nothing appears, stop here, because a timestamp design on top of a hook that never runs will look like a monitoring failure for weeks.

Check the user the hook runs as. With user and group set, OpenVPN drops privileges before running scripts, so a directory root can write may be closed to the hook. Choose the path from the service's point of view rather than your shell's.

Record one epoch value and clear it deliberately

The up hook needs one line, date +%s > /run/openvpn-client.started, with the path adjusted per instance. An epoch value avoids locale, time zone and format questions later, and it is the same number a monitoring agent can compare against its own clock.

Clearing matters as much as writing. The down hook should empty or remove the file, and down-pre makes that happen before the interface closes, which removes the window where the file describes a tunnel that is already gone. up-restart then refreshes the stamp on an internal restart instead of leaving the original value in place. Without that pair, a ping-timeout restart produces a number that keeps climbing while the tunnel is re-establishing.

Pick a location with the right lifetime. A file under /run disappears at reboot, which matches reality, while a file under /tmp may be cleaned unpredictably and one in the configuration directory survives in a way that misleads. Set permissions so only the service user and the monitoring reader can read it.

Compute uptime without trusting log lines

Read the stored second, read the current second, subtract. When the file is absent or empty, the honest answer is that no tunnel is active, not that uptime is zero, because those two states should raise different alerts. Keep raw seconds in the monitoring payload and format days and hours at the display layer. A formatter that rounds inside the exporter throws away precision somebody will want during an incident.

Parsing log timestamps is the tempting shortcut, and it fails three ways: the format follows whatever options are set, rotation removes the history, and reconnects appear as separate sessions rather than one continuous tunnel. The hook records the event once, at the moment it happens.

When the check runs on another host, ship the timestamp rather than the duration so the reader applies its own clock. A small negative result then reads as clock skew instead of a broken hook.

Cover reconnects, restarts and multiple instances

Two tunnels on one host need two files, keyed by the instance name the service already uses. A shared path means the most recent connection overwrites every profile's start time, and the symptom is a number that jumps backwards when a second tunnel starts.

Reconnect behaviour deserves its own test, because it is where a plausible design quietly reports the wrong thing. Force a restart by dropping the peer or signalling the process, then compare the interface state with the file. The timestamp should move forward at the moment the tunnel returns.

On the server side, the status file carries a connected-since value per client, which answers a different question: when the server last saw that peer. Use it for cross-checking rather than as a replacement, since it disappears with the session it describes. Neither measurement says anything about the health of the application behind the tunnel.

Run the three checks that catch ordering bugs

Test a normal connect, a forced reconnect and a clean stop, reading the interface list and the file together after each. A stamp still present after a clean stop, or a file asserting a tunnel the interface listing does not contain, points straight at hook ordering. Run the checks as the service user with the service's paths, because a root shell can see a different file and produce a confident wrong answer. Then leave a slow poller reading the file and confirm it never writes to it.

A poll every sixty seconds covers alerting, and no hook should rely on that poller being alive. Keep those two responsibilities apart and the number stays trustworthy when either side is redeployed.

Artihkkalat mat laktásit