All projects
Security / ● Maintained · v1.1.3

VPNSentinel

Know the moment your VPN stops protecting you.

Monitors VPN connections in real time, catching DNS leaks and silent bypasses with instant Telegram alerts and a traffic light dashboard.

4.9k
Docker pulls
total · Docker Hub
125
Unit tests
116
Integration tests
3
Status states
Green, yellow, red
Synced 0m ago

The problem

A VPN can show “connected” while leaking everything you wanted it to hide. Two failures cause this, and neither is visible to the person using the VPN.

The first is a silent tunnel drop. The connection falls back to direct routing, your real IP is exposed, and the VPN client still shows a green icon because the process is technically running. The second is a DNS leak. Your traffic goes through the tunnel, but DNS queries get answered by your ISP’s resolver, so your browsing history and rough location leak even though the IP looks correct.

Most VPN clients ship no monitoring at all. The ones that do only check whether the tunnel process is alive, which is the one thing that stays true during both failure modes. I wanted something that verifies the VPN is actually doing its job, not just that it is running, and that tells me the instant it stops.

What this does that the others do not

It verifies protection, not connectivity

A ping test confirms a tunnel carries packets. It does not confirm those packets exit where they should. VPNSentinel checks the client’s apparent public IP, its geolocation, and its DNS resolution location, then decides whether the VPN is genuinely protecting traffic. Connectivity is necessary but not the question being asked.

It catches the bypass with a same-IP comparison

The core check is deliberately blunt. The client reports the public IP it sees from inside the VPN container. The server knows its own public IP. If those two match, traffic is not going through the tunnel at all, because the client is exiting from the same place as the unprotected server. That triggers a red critical alert. It is a simple rule, but it reliably catches the worst failure with no false sense of safety.

It separates DNS leaks from IP leaks

A leak is not one thing. The client can have a perfect VPN exit IP and still resolve DNS through the wrong country. VPNSentinel reports these as distinct states: green means the IP is protected and DNS resolves where the VPN exit lives, yellow means the IP is fine but DNS is leaking or unverifiable, red means the IP itself is exposed. Collapsing those into a single up/down flag would hide the most common real-world failure.

It tells you without you looking

Monitoring nobody reads is not monitoring. Every status change goes to Telegram with the client name, IP, location, provider, and DNS detail. A connect message looks different from a DNS leak warning, which looks different from a bypass alarm. The dashboard is there when you want the full picture, but the alert reaches you whether or not you are watching.

Architecture

VPNSentinel is a client-server system, and the network placement of each half is the whole design. The client runs inside the VPN container’s network namespace using Docker’s network_mode: service:vpn-container, so every packet it sends, including its own monitoring traffic, is forced through the tunnel. The server runs on the host network instead, deliberately outside the VPN, because it needs a stable public identity to compare against. The client reaches the server over the public internet rather than a Docker bridge, which means the keepalive itself is proof the tunnel can carry traffic.

The server runs three Flask surfaces and a Telegram bot. The API on port 5000 receives keepalives, the dashboard on 8080 renders traffic lights and a live log viewer, and a health endpoint on 8081 answers container health checks. Geolocation comes from external services such as ipinfo.io, with the server caching its own IP to keep API calls down. Stale clients are auto-removed after a configurable timeout.

        VPN container                          Host network
 +---------------------------+        +----------------------------+
 |  VPN client (OpenVPN/WG)  |        |   VPNSentinel server       |
 |  VPNSentinel client       |        |   API   :5000  keepalives  |
 |   checks public IP        |        |   Web   :8080  dashboard   |
 |   tests DNS leaks         |        |   Health:8081  checks      |
 |   sends keepalives        |        |   Telegram bot  alerts     |
 +-------------+-------------+        +-------------+--------------+
               |  keepalive over the public                |
               |  internet, through the VPN tunnel         |
               +-------------------->----------------------+
                          server compares client IP
                          against its own IP

Decisions worth knowing about

The client lives inside the VPN namespace

I could have run the client as a normal container and pointed it at a VPN. Instead it shares the VPN container’s network namespace directly. This costs some deployment flexibility, because the client is now tied to a specific VPN container’s lifecycle. The payoff is that there is no way for monitoring traffic to escape the tunnel by accident. If the client can reach the internet at all, it did so through the VPN, so a clean keepalive is itself a meaningful signal rather than a separate thing to trust.

The server stays outside the VPN on purpose

Putting the server inside the VPN would have looked tidier and simplified the network setup. It would also have broken the central check. The whole point of comparing client IP to server IP is that the server represents an unprotected baseline. The tradeoff is honest and documented: if you deploy the server on the same network as your VPN exit, you get false bypass warnings. That is a real limitation, and I chose to keep the check simple and explain the constraint rather than add fragile logic to paper over a misconfiguration.

Telegram is the alert channel, the dashboard is the detail

I did not build an email pipeline or a paging integration. A self-hosted privacy tool is usually run by one person, and that person already has Telegram. A bot token and a chat ID are a five minute setup with no SMTP server, no third-party alerting account, and no extra credentials to leak. The dashboard handles depth, with a live log viewer and per-client history, but it is pull, not push. The decision was to make the urgent path trivial to set up and leave the thorough path for when you actually want to dig in.

01 / Quick start

Run it in under a minute

git clone https://github.com/agigante80/VPNSentinel.git
cd VPNSentinel
cp .env.example .env
docker compose up -d