Grafinya Panel
Reference for Grafinya Panel: a web UI and a VPN node in a single container on your server. Installing for the first time? Start with the step-by-step guide — this page has the details.
1Quick start
On a server with Docker:
git clone https://github.com/grapelloid/grafinya-panel.git && cd grafinya-panel
docker compose up -d --buildA few minutes later the panel answers on port 51821. Open it in a browser and create the administrator — that is the wizard’s only question. The panel detects the server’s public address itself; the VPN port is 51820/udp. You can start adding clients right away.
http:// set INSECURE=true (see variables); for everyday use set up a domain and reverse proxy.2Installation (Docker Compose)
Requirements
- A Linux server with a public IPv4 (Ubuntu 22.04/24.04, Debian 12 or similar), 1 GB RAM, ~5 GB disk.
- Docker Engine with the
docker composeplugin. - Open ports:
51820/udp— tunnel,51821/tcp— web panel.
What docker-compose.yml does
| Setting | Value | Why |
|---|---|---|
name | grafinya-panel | fixed project name: the volume is always named the same wherever you clone the repository |
container_name | grafinya | name for docker logs and docker exec |
build | . | the image is built from source on the server; there is no prebuilt image |
volumes | grafinya-panel_grafinya_data | database, server and client keys, settings |
ports | 51820/udp, 51821/tcp | tunnel and web panel |
cap_add | NET_ADMIN, SYS_MODULE | creating the network interface and routing rules |
devices | /dev/net/tun | device for the Grafinya core, which runs in user space — no Linux kernel module needed |
sysctls | ip_forward etc. | forwarding client traffic to the internet (IPv4 and IPv6) |
The Grafinya core runs in user space, so no Linux kernel module is needed; the container gets the /dev/net/tun device for it.
First-run wizard
The wizard only asks for the administrator’s username and password (at least 12 characters). The client-facing address is taken from PUBLIC_HOST, otherwise the auto-detected public IPv4, otherwise the address you opened the panel at. The port is VPN_PORT or 51820. Both can be changed later in Admin → Configuration and Interface.
Unattended installation
For automation (Ansible, cloud-init) you can skip the wizard with the variables below. They are read only on the very first start; remove them from the file afterwards.
environment:
- INIT_ENABLED=true
- INIT_USERNAME=admin
- INIT_PASSWORD=a-long-strong-password
- INIT_HOST=vpn.example.com
- INIT_PORT=51820Optional: INIT_DNS (comma-separated), INIT_IPV4_CIDR together with INIT_IPV6_CIDR, INIT_ALLOWED_IPS. Password strength is not checked here — pick a long one yourself.
3Settings and environment variables
Variables go into the environment: block of the grafinya service in docker-compose.yml. Run docker compose up -d after editing.
| Variable | Default | What it does |
|---|---|---|
PORT | 51821 | web panel TCP port inside the container |
HOST | 0.0.0.0 | address the web panel listens on |
INSECURE | false | true — allow sign-in over HTTP without TLS (testing only) |
PUBLIC_HOST | auto | domain or IP written into client configs |
VPN_PORT | 51820 | tunnel UDP port; applied on every start if set. Change together with ports: |
OVERRIDE_AUTO_AWG | — | wg — turn off Grafinya obfuscation (no traffic masking) |
DISABLE_IPV6 | false | true — no IPv6 for clients and no IPv6 rules |
DISABLE_VERSION_CHECK | false | true — don’t check for updates |
TRUSTED_PROXIES | — | comma-separated IPs/subnets of your reverse proxy; trusts X-Forwarded-* headers and enables Origin checks |
WG_INTERFACE | wg0 | tunnel network interface name (needed when running several panels on one host) |
DOCS_URL | — | “Documentation” link in the panel footer (e.g. to this site) |
SOURCE_URL | — | link to the source code of your panel version |
DISABLE_PASSWORD_AUTH | false | true — sign in only via an external OAuth/OIDC provider |
Everything else — client DNS, subnets, MTU, keepalive, allowed IPs, the client-to-client firewall — is configured in the web UI: Admin → Configuration and Interface.
4Domain and HTTPS
The panel is meant to run behind a reverse proxy with a TLS certificate: browser → https://panel.example.com → proxy → 127.0.0.1:51821. First point the domain’s A record at the server IP and open ports 80 and 443.
Caddy (certificate issued automatically)
panel.example.com {
reverse_proxy 127.0.0.1:51821
}nginx
server {
listen 443 ssl http2;
server_name panel.example.com;
ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:51821;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
}
}Get a certificate for nginx with certbot --nginx -d panel.example.com.
Close direct access to 51821
Once the proxy works, bind the panel port to localhost — otherwise it is still reachable at http://IP:51821 (Docker publishes ports bypassing ufw):
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"Keep INSECURE=false when serving over HTTPS. If the proxy runs in a Docker network rather than on the host, add its address to TRUSTED_PROXIES.
5Clients and .conf files
A client is one device with its own key pair and tunnel address. Don’t use one config on several devices at the same time.
- Create: Clients → New client → name and (optionally) an expiry date.
- .conf file: “Download configuration” on the client card. The grafinya app takes it via “Choose file”.
- QR code — for clients on other platforms that can scan a config.
- One-time link — to hand a config to someone without panel access; the link is valid for 5 minutes and for at most 10 seconds after the first download.
- Expiry: the client is disabled after the date. To extend, just set a new date.
- Enable/disable — the switch on the card cuts access immediately without changing the config.
- Statistics: online status, last handshake, traffic and Tx/Rx charts.
From the server console:
docker exec -it grafinya cli clients:list
docker exec -it grafinya cli clients:qr 16Obfuscation parameters
Obfuscation is a Grafinya core feature that makes tunnel traffic look unlike a typical VPN: it sends junk packets before the handshake, pads control messages with random bytes and replaces packet headers. It is on by default, and the random values are generated per server on first start.
| Parameter | Default | Meaning | Must match on server and client |
|---|---|---|---|
Jc | 7 | number of junk packets before the handshake | may differ |
Jmin / Jmax | 10 / 1000 | minimum and maximum junk packet size, bytes | may differ |
S1, S2 | 128, 56 | random padding of handshake initiation and response, bytes | must match |
S3, S4 | — | padding of the other control and transport packets | must match |
H1–H4 | random | replacement headers for the four packet types | must match |
I1–I5 | — | signature packets that mimic other UDP protocols | may differ |
The panel writes the must-match parameters into every .conf. So after changing them in Admin → Interface, download the client configs again — old ones will stop connecting.
If you edit them by hand: keep Jmin < Jmax and Jmax under ~1280 to avoid fragmentation; H1–H4 must all differ. When in doubt, keep the values the panel generated.
To turn obfuscation off entirely, set OVERRIDE_AUTO_AWG=wg — traffic is then not masked and client configs are created without these parameters.
7Updates, backups, restore
Update
cd ~/grafinya-panel
git pull
docker compose up -d --build
docker image prune -fData lives in the grafinya-panel_grafinya_data volume and survives rebuilds.
Backup
cd ~/grafinya-panel
docker compose stop
docker run --rm -v grafinya-panel_grafinya_data:/data -v "$PWD":/backup alpine \
tar czf /backup/grafinya-backup-$(date +%F).tar.gz -C /data .
docker compose startRestore
On the new server install the panel without going through the wizard, then:
cd ~/grafinya-panel
docker compose stop
docker run --rm -v grafinya-panel_grafinya_data:/data -v "$PWD":/backup alpine \
sh -c "rm -rf /data/* && tar xzf /backup/grafinya-backup-DATE.tar.gz -C /data"
docker compose startIf the new server has a different IP, change the address in Admin → Configuration and hand out new configs (or use a domain in PUBLIC_HOST — then moving the DNS record is enough).
8Security
- Admin password — at least 12 characters, unique, kept in a password manager.
- 2FA: enable two-factor authentication (TOTP) on the Account page (user menu, top right). Any authenticator app works.
- HTTPS only:
INSECURE=false, panel behind a reverse proxy, port51821bound to127.0.0.1. - Minimal open ports: SSH,
51820/udp, and 80/443 for the proxy. - SSH: key-based login, root password login disabled (
PasswordAuthentication no). - Updates: keep the OS (
apt upgrade) and the panel up to date. - Backups contain every private key — store them encrypted.
- Access reset:
docker exec -it grafinya cli db:admin:resetsets a new password and disables the admin’s 2FA.
9Troubleshooting
Start with the logs and container state:
docker ps
docker logs --tail 100 grafinya| Symptom | What to check |
|---|---|
| Panel doesn’t open | container is running; 51821/tcp is open in the provider firewall; curl -I http://127.0.0.1:51821 on the server |
| Page loads but sign-in fails | HTTP without INSECURE=true; behind a proxy — the X-Forwarded-Proto header and TRUSTED_PROXIES |
| Client connects but no internet | 51820/udp is open (UDP!); the address in Configuration is right; forwarding sysctls are set |
| Connection drops after a minute | S1–S4/H1–H4 mismatch: download the config again after changing parameters |
Logs mention /dev/net/tun | the panel was built from an old version: update the repository (git pull && docker compose up -d --build) |
| Image build fails | out of memory: add 1–2 GB swap and rerun docker compose up -d --build |
| Port 51820 is taken | set VPN_PORT and the left side of ports: to the same new value |
10FAQ
Can I install the panel without Docker?
Only Docker Compose is officially supported: that way the core, the panel and all dependencies build identically on any server.
How many clients can a server handle?
The limit is the subnet (/24 by default — about 250 clients) and your VPS bandwidth. The tunnel itself uses little CPU.
Does Grafinya see my traffic?
No. The panel and tunnel run on your server; traffic flows between your device and your VPS. The app sends only an installation ID, errors and diagnostics — see the policy.
Can I use the config in other apps?
Yes, if the app supports the obfuscation parameters in the file. On iPhone we recommend grafinya.
How do I change the tunnel port?
Set VPN_PORT=51999 and "51999:51999/udp" in ports:, run docker compose up -d, open the new port in the firewall and hand out new configs.
Where is the source code?
The panel is on GitHub (AGPL-3.0). Third-party components and their licenses are listed on the Contributors & licenses page.