Grafinya
Documentation
  1. Quick start
  2. Installation (Docker Compose)
  3. Settings and variables
  4. Domain and HTTPS
  5. Clients and .conf files
  6. Obfuscation parameters
  7. Updates and backups
  8. Security
  9. Troubleshooting
  10. FAQ
Documentation

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:

terminal
git clone https://github.com/grapelloid/grafinya-panel.git && cd grafinya-panel
docker compose up -d --build

A 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.

HTTPS is expected. By default the panel only allows sign-in over HTTPS. For a quick test over 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 compose plugin.
  • Open ports: 51820/udp — tunnel, 51821/tcp — web panel.

What docker-compose.yml does

SettingValueWhy
namegrafinya-panelfixed project name: the volume is always named the same wherever you clone the repository
container_namegrafinyaname for docker logs and docker exec
build.the image is built from source on the server; there is no prebuilt image
volumesgrafinya-panel_grafinya_datadatabase, server and client keys, settings
ports51820/udp, 51821/tcptunnel and web panel
cap_addNET_ADMIN, SYS_MODULEcreating the network interface and routing rules
devices/dev/net/tundevice for the Grafinya core, which runs in user space — no Linux kernel module needed
sysctlsip_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.

docker-compose.yml
    environment:
      - INIT_ENABLED=true
      - INIT_USERNAME=admin
      - INIT_PASSWORD=a-long-strong-password
      - INIT_HOST=vpn.example.com
      - INIT_PORT=51820

Optional: 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.

VariableDefaultWhat it does
PORT51821web panel TCP port inside the container
HOST0.0.0.0address the web panel listens on
INSECUREfalsetrue — allow sign-in over HTTP without TLS (testing only)
PUBLIC_HOSTautodomain or IP written into client configs
VPN_PORT51820tunnel UDP port; applied on every start if set. Change together with ports:
OVERRIDE_AUTO_AWG—wg — turn off Grafinya obfuscation (no traffic masking)
DISABLE_IPV6falsetrue — no IPv6 for clients and no IPv6 rules
DISABLE_VERSION_CHECKfalsetrue — don’t check for updates
TRUSTED_PROXIES—comma-separated IPs/subnets of your reverse proxy; trusts X-Forwarded-* headers and enables Origin checks
WG_INTERFACEwg0tunnel 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_AUTHfalsetrue — 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)

/etc/caddy/Caddyfile
panel.example.com {
    reverse_proxy 127.0.0.1:51821
}

nginx

/etc/nginx/sites-available/grafinya
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):

docker-compose.yml
    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:

CLI
docker exec -it grafinya cli clients:list
docker exec -it grafinya cli clients:qr 1
A .conf file contains a private key. Don’t publish it. If it leaks, delete the client and create a new one.

6Obfuscation 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.

ParameterDefaultMeaningMust match on server and client
Jc7number of junk packets before the handshakemay differ
Jmin / Jmax10 / 1000minimum and maximum junk packet size, bytesmay differ
S1, S2128, 56random padding of handshake initiation and response, bytesmust match
S3, S4—padding of the other control and transport packetsmust match
H1–H4randomreplacement headers for the four packet typesmust match
I1–I5—signature packets that mimic other UDP protocolsmay 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

terminal
cd ~/grafinya-panel
git pull
docker compose up -d --build
docker image prune -f

Data lives in the grafinya-panel_grafinya_data volume and survives rebuilds.

Backup

terminal
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 start

Restore

On the new server install the panel without going through the wizard, then:

terminal
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 start

If 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, port 51821 bound to 127.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:reset sets a new password and disables the admin’s 2FA.

9Troubleshooting

Start with the logs and container state:

terminal
docker ps
docker logs --tail 100 grafinya
SymptomWhat to check
Panel doesn’t opencontainer 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 failsHTTP without INSECURE=true; behind a proxy — the X-Forwarded-Proto header and TRUSTED_PROXIES
Client connects but no internet51820/udp is open (UDP!); the address in Configuration is right; forwarding sysctls are set
Connection drops after a minuteS1–S4/H1–H4 mismatch: download the config again after changing parameters
Logs mention /dev/net/tunthe panel was built from an old version: update the repository (git pull && docker compose up -d --build)
Image build failsout of memory: add 1–2 GB swap and rerun docker compose up -d --build
Port 51820 is takenset 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.