Building a Unified SSH and VPN Connection Manager for Linux
Building a Unified SSH and VPN Connection Manager for Linux
As a software engineer, I frequently connect to remote servers, establish SSH tunnels, and switch between different VPN environments.
Although tools like SSH, OpenVPN, and OpenConnect are powerful, repeatedly entering connection parameters, usernames, passwords, and two-factor authentication codes can become tedious.
I wanted a simpler workflow: one command to manage all my SSH and VPN connections, integrated with KeePassXC for credential management.
The result is a shell utility called tun, built using shell functions, fzf, SSH, KeePassXC, and OpenConnect.
What Does It Do?
The tun command provides a unified interface for:
- Regular SSH connections
- Local SSH port forwarding (
-L) - Reverse SSH port forwarding (
-R) - SOCKS5 proxies (
-D) - OpenConnect and OpenVPN connections
- Automatic credential retrieval from KeePassXC
- Optional TOTP-based two-factor authentication
- Automatic VPN reconnection after session expiration
It supports both interactive selection through fzf and direct command-line arguments.
1. A Unified Command-Line Interface
Instead of remembering different commands for different connection types, I can simply execute:
1 | tun |
This opens an interactive fzf menu:
1 | Select Connection Type |
After selecting a connection type, the script displays available hosts or VPN configurations.
For example, choosing SSH displays the aliases and addresses defined in ~/.ssh/config:
1 | Select SSH Host |
The script uses ssh -G to retrieve effective hostnames, so configuration options such as HostName, Port, and ProxyJump are respected.
For my setup, only SSH aliases containing / appear in the interactive selection menu. This allows me to organize hosts by environment.
Direct Command-Line Arguments
Sometimes an interactive menu is unnecessary, especially when repeating a connection.
The script also supports direct arguments:
1 | # Regular SSH connection |
This makes the same utility useful for both occasional interactive connections and frequently used commands.
2. Managing SSH Hosts with SSH Config
Instead of maintaining a separate hosts database, the script reads ~/.ssh/config.
Example:
1 | Host work/kubernetes |
Using SSH configuration provides several advantages:
- Connection details stay in one place.
- Standard SSH features work without additional implementation.
- Configuration changes are reflected when the script runs.
- Authentication can be configured independently for each server.
SSH Key Detection
One requirement was avoiding unnecessary password prompts for servers configured with SSH keys.
The script checks whether an explicit IdentityFile is configured.
If a key is configured, it uses ordinary SSH authentication without retrieving credentials from KeePass.
Otherwise, it attempts to retrieve a username and password from KeePassXC.
3. Integrating KeePassXC for SSH Credentials
I use KeePassXC to manage credentials, so I wanted the connection utility to retrieve them automatically.
My KeePass database follows a structure that mirrors SSH host aliases:
1 | SSH/ |
For example, selecting work/kubernetes makes the script search:
1 | keepass ls -R -f "SSH/work/kubernetes" |
The keepass command is a custom shell function wrapping keepassxc-cli with my default database path.
Automatic Credential Selection
The script handles three scenarios:
No credentials found: It asks for a username and password.
One credential found: It automatically selects that credential.
Multiple credentials found: It displays an fzf menu containing entry names and usernames.
1 | Select SSH Credential |
After selection, it retrieves both attributes:
1 | keepass show \ |
Passing Passwords to SSH
OpenSSH doesn’t provide a standard command-line option for supplying passwords non-interactively.
For password-based connections, I use sshpass with a dedicated file descriptor:
1 | sshpass -d 3 ssh \ |
Unlike sshpass -p, this avoids placing the password directly in the process command-line arguments.
SSH key authentication is still preferable where available, but this provides a practical integration with an existing password manager.
4. Managing VPN Profiles with OpenConnect and OpenVPN
The script supports two VPN configuration extensions:
| Extension | VPN client |
|---|---|
.conf |
OpenConnect |
.ovpn |
OpenVPN |
My VPN directory looks like this:
1 | ~/vpn/ |
Running:
1 | tun vpn |
opens an fzf menu containing these profiles.
Alternatively, I can connect directly:
1 | tun vpn work |
The script automatically selects work.conf when it uniquely matches the supplied name.
OpenConnect Configuration
OpenConnect supports several VPN protocols, including Cisco AnyConnect, Fortinet, and GlobalProtect.
Rather than hardcoding protocol-specific settings in the shell function, I store them directly in each VPN profile.
Example work.conf:
1 | server=vpn.work.com:443 |
This example also uses vpn-slice for split tunneling, allowing selected networks to be routed through the VPN rather than sending all traffic through it.
The script directive requires vpn-slice to be installed at the specified location. Other options, such as no-external-auth, should be enabled only when compatible with the VPN gateway.
5. Automatic VPN Authentication with KeePassXC
For VPN connections, I use a simpler KeePass structure than for SSH:
1 | VPN/ |
The script matches the VPN configuration filename, without its extension, to the KeePass entry title.
For example:
1 | VPN profile: ~/vpn/work.conf |
This avoids opening another credential-selection menu.
If the matching KeePass entry cannot be retrieved, the script launches the VPN client normally and lets it handle authentication interactively.
Supporting Two-Factor Authentication
Some VPN servers require a password followed by a separate time-based one-time password (TOTP).
KeePassXC can generate these codes when a TOTP secret is configured for the entry.
The script attempts to retrieve all three values:
1 | keepass show \ |
If TOTP isn’t configured, KeePassXC may return an error. In that case, the script retries without -t and continues with the username and password.
This makes TOTP optional on a per-entry basis.
6. Automatic VPN Reconnection
Another problem was session expiration.
OpenConnect provides a reconnect-timeout option for recovering interrupted connections. However, once a VPN authentication session expires, a new login may be required.
To handle this, I added a reconnection supervisor.
The workflow is:
- Connect to the VPN.
- Keep the VPN client running in the foreground.
- Detect when the VPN process exits.
- Retrieve credentials and a fresh TOTP code.
- Wait five seconds.
- Attempt to establish a new VPN session.
The retry loop is conceptually simple:
1 | while :; do |
The implementation also tracks connection duration and stops after three rapid failures, reducing the risk of repeated authentication failures.
Reconnection begins only after the VPN client exits. The script does not independently monitor the authentication cookie’s expiration time.
For unattended reconnection, KeePass must remain accessible, and any required privileges or MFA approvals must still be satisfied.
7. Complete Installation Guide
Here is an end-to-end installation guide for Fedora, Arch Linux and Ubuntu/Debian. The shell functions support both Bash and Zsh.
Step 1: Install Dependencies
Fedora:
1 | sudo dnf install \ |
Arch Linux:
1 | sudo pacman -Syu --needed \ |
Ubuntu / Debian:
1 | sudo apt update |
Step 2: Create the Directory Structure
Create the directories for your configuration files and shell functions:
1 | mkdir -p \ |
The resulting structure will look like this:
1 | ~/ |
Step 3: Install the Connection Script
Save the complete conn-tun.sh script containing the tun and conn-vpn functions.
From the directory containing the downloaded script, run:
1 | install -m 600 ./conn-tun.sh \ |
The script is designed to be sourced into your existing shell rather than launched as a separate executable.
Step 4: Configure the KeePassXC Helper
I use a small shell function to avoid specifying the KeePass database path every time.
Add the following to ~/.zshrc or ~/.bashrc:
1 | keepass() { |
Replace ~/Vault.kdbx with the actual location of your KeePass database.
Now, instead of running:
1 | keepassxc-cli ls ~/Vault.kdbx SSH |
I can simply use:
1 | keepass ls SSH |
Test the helper:
1 | keepass ls -f VPN |
KeePassXC may request the database master password when executing CLI commands, even if the graphical application is already unlocked.
Step 5: Configure SSH Hosts
Create or update ~/.ssh/config:
1 | # Kubernetes server - password authentication |
Apply appropriate file permissions:
1 | chmod 700 ~/.ssh |
Verify that OpenSSH can read the configuration:
1 | ssh -G work/kubernetes | |
Expected output:
1 | hostname 10.0.0.10 |
The tun function will use these aliases automatically.
Step 6: Configure KeePassXC SSH Credentials
Open your KeePassXC database and create the following groups and entries:
1 | SSH/ |
Example fields for the Administrator entry:
| Field | Value |
|---|---|
| Title | Administrator |
| Username | ubuntu |
| Password | Your SSH password |
The matching group is determined by the SSH alias:
1 | SSH alias: work/kubernetes |
You can verify the entry using:
1 | keepass ls -R -f "SSH/work/kubernetes" |
And retrieve its username:
1 | keepass show \ |
For homelab/proxmox, which uses an SSH key, a KeePass entry isn’t necessary.
Step 7: Create VPN Configurations
For OpenConnect, create ~/vpn/work.conf:
1 | cat > "$HOME/vpn/work.conf" <<'EOF' |
Replace the example address and options with those required by your VPN gateway.
Additional options can be added when needed:
1 | useragent=FortiSSLVPN |
If you use servercert, obtain and verify the actual server certificate pin through a trusted source. Do not use the placeholder shown above.
For OpenVPN, copy a valid .ovpn configuration supplied by your VPN administrator:
1 | cp /path/to/your/openvpn-profile.ovpn \ |
OpenVPN and OpenConnect use different configuration formats, so keep their files separate.
Step 8: Configure KeePassXC VPN Credentials
In KeePassXC, create a group called VPN with an entry named work.
1 | VPN/ |
Example fields:
| Field | Value |
|---|---|
| Title | work |
| Username | employee |
| Password | Your VPN password |
| TOTP | Optional TOTP secret |
To configure TOTP in KeePassXC, open the entry and use its TOTP configuration option to add the secret provided during your VPN MFA enrollment.
The filename and KeePass entry title must match:
1 | VPN profile: work.conf |
Test retrieval:
1 | keepass show \ |
If you configured TOTP:
1 | keepass show -t "VPN/work" |
Do not print real credentials or OTP codes in shared terminals, screenshots, or logs.
Step 9: Load the Shell Functions
Add this line to ~/.zshrc:
1 | source "$HOME/.config/shell/conn-tun.sh" |
For Bash, add it to ~/.bashrc instead.
Reload your shell configuration:
1 | source ~/.zshrc |
Verify that both functions are available:
1 | type tun |
All three should be recognized as shell functions.
Step 10: Prepare VPN Permissions
The VPN script launches OpenConnect or OpenVPN using sudo -n, which does not prompt for a password.
For an initial test, authenticate sudo first:
1 | sudo -v |
For unattended reconnection after the sudo authentication ticket expires, the VPN process needs an appropriate privilege arrangement.
Avoid giving arbitrary user-writable scripts or VPN configurations unrestricted root execution through broad NOPASSWD rules. A controlled root-owned launcher or narrowly scoped service is a safer option.
8. Complete Usage Examples
With installation finished, here are practical examples of using the utility.
Example 1: Connect to a Server with KeePassXC
Execute:
1 | tun ssh work/kubernetes |
The script performs the following actions:
- Resolves
work/kubernetesusing~/.ssh/config. - Checks whether an explicit
IdentityFileexists. - Searches
SSH/work/kubernetesin KeePassXC. - Automatically selects the credential if there is only one entry.
- Connects using the retrieved username and password.
Example output:
1 | Looking for KeePass entries: SSH/work/kubernetes |
No additional fzf selection is needed because the host was supplied directly.
Example 2: Connect Using an SSH Key
Execute:
1 | tun ssh homelab/proxmox |
Because homelab/proxmox has an IdentityFile configured, the script skips KeePass credential retrieval and uses the configured SSH identity.
This is useful for frequently accessed infrastructure servers.
Example 3: Create a Local SSH Tunnel
Suppose a remote server exposes an HTTPS service on port 443, but you want to access it through local port 9191.
Execute:
1 | tun local work/kubernetes 9191 443 |
The resulting forwarding operation is equivalent to:
1 | ssh -N -T \ |
Now test the connection locally:
1 | curl -vk https://127.0.0.1:9191 |
You can also specify another destination host reachable from the SSH server:
1 | tun local work/kubernetes 9191 443 10.0.0.50 |
This forwards your local port 9191 to 10.0.0.50:443 through the SSH server.
Example 4: Create a Reverse SSH Tunnel
Suppose you’re running a development application locally on port 3000, and you want to expose it to the remote SSH server on port 9000.
Execute:
1 | tun reverse work/kubernetes 9000 3000 |
The equivalent SSH command is:
1 | ssh -N -T \ |
From the remote SSH server, test:
1 | curl http://127.0.0.1:9000 |
Reverse forwarding requires the server’s SSH daemon to allow remote TCP forwarding.
A restrictive configuration can use:
1 | AllowTcpForwarding remote |
The tunnel also remains subject to any PermitListen, key-specific forwarding restrictions, and port conflicts.
Example 5: Create a SOCKS5 Proxy
Execute:
1 | tun socks work/kubernetes |
This creates a local SOCKS5 proxy on port 9090.
Equivalent SSH command:
1 | ssh -N -T \ |
To test it:
1 | curl \ |
The request is routed through the SSH server.
Using --socks5-hostname also delegates hostname resolution to the proxy side.
To use a different SOCKS5 port:
1 | tun socks work/kubernetes 1080 |
Example 6: Connect to a VPN Automatically
Execute:
1 | tun vpn work |
The script finds ~/vpn/work.conf, then searches for the corresponding KeePass entry:
1 | VPN/work |
Example output:
1 | Selected VPN: work.conf |
OpenConnect receives the username and password from KeePass, along with a TOTP code when configured.
If the entry isn’t available, OpenConnect starts directly and handles authentication interactively.
Example 7: Reconnect After VPN Session Expiration
When the VPN authentication session expires and OpenConnect exits with a failure, the script retries automatically.
Example:
1 | Selected VPN: work.conf |
A fresh TOTP is retrieved for each authentication attempt.
The script stops after three rapid connection failures, preventing an endless sequence of unsuccessful authentication attempts.
Successful unattended reconnection still depends on the availability of KeePass credentials, necessary privileges, and the VPN server’s MFA policy.
Example 8: Use the Interactive Menu
When I don’t remember the exact connection name, I simply execute:
1 | tun |
After selecting VPN, an example profile menu looks like this:
1 | Select VPN Configuration |
After selecting SSH Connection, the host menu looks like this:
1 | Select SSH Host |
This is particularly useful when working with many different environments.
9. Troubleshooting
KeePassXC Keeps Asking for the Master Password
keepassxc-cli opens the KeePass database independently of the graphical KeePassXC application.
The connection script doesn’t bypass database authentication. A secure unlocked credential provider or a persistent CLI session is needed to avoid repeated master-password prompts.
SSH Reports “Administratively Prohibited”
If an SSH tunnel reports:
1 | channel 1: open failed: administratively prohibited: open failed |
Check the SSH server’s forwarding configuration:
1 | sudo sshd -T | |
The server may be configured to reject TCP forwarding.
Reverse Port Forwarding Fails
If a reverse tunnel reports:
1 | Error: remote port forwarding failed for listen port 9090 |
Check whether the port is occupied:
1 | sudo ss -ltnp | grep ':9090' |
Also verify that the SSH server allows remote forwarding.
OpenConnect Cannot Reconnect
Check whether OpenConnect has exited or is still attempting to recover the existing session.
The wrapper only starts a new authentication attempt after the VPN process exits.
Also check sudo permissions, KeePass availability, and whether the VPN requires additional interactive authentication.
10. Security Considerations
Automating SSH and VPN authentication introduces several security considerations.
The main principles I followed were:
- Avoid hardcoded passwords. Credentials remain in KeePassXC rather than in scripts.
- Prefer SSH keys. Hosts configured with an explicit
IdentityFilebypass password retrieval. - Avoid passwords in command-line arguments. SSH credentials are passed through file descriptors.
- Preserve SSH host-key verification. The script does not disable SSH server identity checks.
- Limit connection retries. Repeated rapid VPN failures cause the supervisor to stop.
- Avoid shell tracing. Running credential-handling functions with
set -xcould expose sensitive values. - Restrict privileged operations. VPN network configuration should not allow arbitrary untrusted scripts to execute as root.
Credentials still exist temporarily in shell memory, and automation does not eliminate the security implications of storing passwords and TOTP secrets together.
Bonus: Unlocking KeePassXC Automatically with Secret Service
One inconvenience with keepassxc-cli is that it normally asks for the database master password every time a command is executed.
This becomes particularly annoying when the connection manager needs multiple KeePass operations or automatically reconnects to a VPN after session expiration.
To solve this, I integrated secret-tool with keepassxc-cli.
How It Works
secret-tool communicates with the Linux Secret Service API, which is commonly provided by GNOME Keyring or KDE Wallet.
Instead of entering the KeePassXC database master password every time, we can securely store it in the desktop keyring and retrieve it when executing CLI commands.
The workflow is:
1 | tun |
This approach doesn’t require modifying the tun or conn-vpn functions.
Step 1: Install Secret Service Tools
Fedora:
1 | sudo dnf install libsecret |
Arch Linux:
1 | sudo pacman -S --needed libsecret |
Ubuntu / Debian:
1 | sudo apt install libsecret-tools |
Make sure your desktop session has an available Secret Service provider, such as GNOME Keyring.
Verify the installation:
1 | command -v secret-tool |
Step 2: Configure the KeePassXC Database Path
Add the following environment variable to your ~/.zshrc or ~/.bashrc:
1 | export KEEPASS_DB_PATH="$HOME/Vault.kdbx" |
Replace the path with the location of your actual KeePassXC database.
Reload your shell configuration:
1 | source ~/.zshrc |
Step 3: Store the KeePassXC Master Password
Store your database master password in the system keyring:
1 | secret-tool store \ |
The command prompts for the password securely through the terminal.
The stored secret is identified by two attributes:
| Attribute | Value |
|---|---|
application |
keepassxc |
database |
Path specified in KEEPASS_DB_PATH |
These attributes allow us to retrieve the correct password later.
The keyring must be unlocked for the password to be retrieved without additional interaction.
Step 4: Create the KeePass Helper Script
Create an executable named keepass in ~/.local/bin/keepass.
1 | mkdir -p "$HOME/.local/bin" |
Use the following script:
1 |
|
Make it executable:
1 | chmod 700 "$HOME/.local/bin/keepass" |
Ensure ~/.local/bin is included in your PATH. If you previously defined a keepass shell function, remove it or update it to invoke this executable.
Step 5: Test the Integration
List the KeePass groups:
1 | keepass ls |
Retrieve SSH credentials:
1 | keepass ls -R -f "SSH/work/kubernetes" |
Retrieve a username:
1 | keepass show \ |
Retrieve a VPN TOTP code:
1 | keepass show -t "VPN/work" |
With the system keyring unlocked and the correct master password stored, these commands can execute without prompting for the KeePassXC master password each time.
Security Considerations
This approach improves convenience but changes the security model.
The KeePassXC database master password is now stored in a second credential store. Anyone or any process that can retrieve that secret from the unlocked keyring could potentially unlock the database.
It is important to keep the following in mind:
- Use a password-protected system keyring.
- Ensure the keyring locks when the user session is locked or terminated, according to your security requirements.
- Avoid storing the master password in shell history, environment variables, or plaintext configuration files.
- Run the KeePass helper as your normal user, not through
sudo. - Prefer an independent Secret Service provider, such as GNOME Keyring, for this approach. Using KeePassXC itself as the provider for its own master password creates a circular dependency when the database is locked.
For environments with stronger security requirements, manually unlocking KeePassXC or using dedicated hardware-backed authentication may be preferable.
Final Result
With this integration, the entire connection workflow becomes:
1 | # Interactive connection selection |
The connection manager can retrieve credentials using the same keepass interface without repeatedly asking for the database master password, provided the system keyring is available and unlocked.
This is especially useful for automatic VPN reauthentication, where the script needs to retrieve a new TOTP code after the previous VPN session expires.
Conclusion
What started as a few SSH aliases eventually became a small connection-management utility.
By combining OpenSSH, OpenConnect, OpenVPN, KeePassXC, sshpass, and fzf, I eliminated much of the repetitive work involved in connecting to remote infrastructure.
The most useful improvements have been:
- A single command for different connection types.
- Centralized SSH and VPN configuration.
- Creating SSH tunnels and SOCKS5 proxy
- Automatic credential retrieval from KeePassXC.
- Optional TOTP authentication.
- VPN reconnection with fresh authentication credentials.
- Interactive and non-interactive connection workflows.
The implementation relies mostly on existing Linux tools rather than introducing a new connection-management service.
For anyone who frequently switches between servers, SSH tunnels, and VPN environments, a lightweight shell-based solution like this can make everyday infrastructure work considerably more convenient.