The card puts the address websites see next to the server's address.
Through a full tunnel both are the server's, and the card says
"You are protected"; directly or through a split tunnel it says
"Not protected", with the visitor's address and its location.
In a terminal, install now says which peers it waits for and that Enter
skips the wait; Enter ends it at once and names the peers not back yet.
-no-wait skips it in scripts. Without a terminal and without -no-wait,
install still waits up to 30 s. The takeover is done before the wait
starts, so skipping it changes nothing on the server.
On a server running pivpn's WireGuard, a new install offers to take it
over: the server key, port, MTU, tunnel networks, endpoint, DNS,
AllowedIPs and keepalive, and every client with its public key,
preshared key and addresses. Devices keep their configs. Clients pivpn
switched off are imported switched off, with the note "Imported from
pivpn". Client private keys in /etc/wireguard/configs are not read.
- Install notes which peers are connected, stops wg-quick@wg0, starts
GHOSTWIRE on the same wg0 and waits up to 30 s for those peers. The
wait only reports; idle devices reconnect when they next send.
- If the service does not stay running, install removes what it set up,
including config.json, and starts pivpn's WireGuard again.
- Without a terminal the takeover needs -import-pivpn; install refuses
to run next to pivpn otherwise, and the flag is refused on an
existing install.
- Names GHOSTWIRE does not accept are renamed and listed in the
summary. An IPv6 address that differs from the mapped one is kept on
the peer until its config is issued again.
- uninstall without a config of its own (e.g. after a takeover was
undone) leaves the WireGuard interface alone and removes only the
firewall table.
- README: "Coming from pivpn?" under the intro, a Features entry and a
"Moving from pivpn" section.
Tested end to end on Ubuntu 24.04 with pivpn aa96de7.
The Server config and Add peer buttons are gone from the Dashboard
heading. Server is in the sidebar, Add peer is on the Peers page, and
with no peers yet the Peers card still links to adding the first one.
- The server endpoint must be a plain host name or IP address. It is
written into client configs as is, so a newline could add lines such
as PreUp, which wg-quick runs as root on the client.
- Listen addresses and the session length (1–720 hours) are checked.
Before web settings or a restore are saved, the server tries the new
listen addresses and certificate files, so a value it cannot start
with is refused instead of stopping the service at the next restart.
- Kernel applies run one at a time and read the config once it is
their turn, so an older config can no longer be applied last.
- Pending passkey sign-ins are capped: 10 per address, 1000 in total.
- Behind a local proxy, the last X-Forwarded-For entry is the client;
earlier ones come from the client and are ignored.
- With LAN access off, peers are also kept from the IPv6 networks on
the uplink, not only from its private IPv4 networks.
- A change that leaves no user with a password is refused, and so is a
backup without one or from a newer version.
GET /api/v1/status names the check Kernel in sync, the same as the
health row in the web interface, which now reads the new name directly.
The detail is unchanged: "applied <time>" or the kernel's error.
The health row that shows when the config was last written to the
kernel is now called Kernel in sync, with the time since then, or Out
of sync and the kernel's error when applying failed. The API keeps the
check's name, so clients are unaffected.
Links to another page (All peers, Log, Add the first one) are ink with
an arrow that nudges on hover, the back link gets ←, and links that
open an outside page get ↗ and "opens in a new tab" for screen
readers, on the setup page too. Peer names look the same everywhere,
and links in text get a pale underline. Buttons and the sidebar stay
as they are.
The chart draws download and upload as smooth curves that never dip
below zero or overshoot a peak, and speeds always show one decimal
from kbit/s up so the figures keep their shape as they change.
The big figures, the per-peer speeds and the busiest-first order
average the last 5 steps instead of swinging with every burst, and the
figures sit in fixed-width columns so they no longer push each other
around. The charts still show every step.
The server pushes each new step over server-sent events
(GET /api/v1/live/stream) the moment it is sampled, so updates no
longer arrive in uneven pairs. The chart slides left steadily between
steps instead of jumping, and the big numbers count to their new
value. Both stay still with reduced motion.
A new Live page shows current download and upload per peer, updated
every 2 seconds, with the last 2 minutes as a chart and a small chart
per peer. The server reads the WireGuard counters every 2 seconds and
keeps 2 minutes in memory; GET /api/v1/live serves them, with since=
for only the newer steps. The dev simulator now adds traffic in
proportion to the time between samples.
Traffic and latency charts label the x-axis with clock times every
3 hours (6 on phones, the date at midnight), or dates for the 7- and
30-day ranges. The hover readout for hourly bars shows the clock
time range instead of "3 h ago".
Settings -> Upkeep -> Backup & restore lists the config.json.bak-* files
that update leaves behind and removes one or all of them; they hold the
same secrets as a backup. Removing needs a signed-in user and is logged.
After a successful update only the newest 3 copies are kept, and a copy
that would overwrite an older one (version unknown, or the same version
twice) gets the time appended. The backup card now also names preshared
keys and authenticator app secrets.
The update notice uses the existing compareVersions instead of its own.
Settings is grouped into Access (users, sign-in, iOS app and API tokens),
Web interface (address and HTTPS), Logs & history and Upkeep
(updates, backup). Session length moved to Sign-in and no longer asks for
a restart. Log level, log size and traffic history share one card; the
country lookup is its own switch. Each card says how it saves.
The log viewer moved to a new Log page in the sidebar, with a filter for
changes only, and the Dashboard's Log link opens it.
Once a day the server asks Gitea or GitHub, as picked under Settings ->
Updates, for the latest release. A newer one shows as a pill in the
sidebar, a banner on the Dashboard and in the Updates card with its
release notes and the commands to update this server. Drafts and
pre-releases are ignored, nothing about the server is sent, and the check
can be switched off. POST /updates/check checks now.
The public addresses stack on the left and the other checks are rows in
one list on the right, each with its raw setting right after the status.
Below 1000px the addresses move above the list.
The public IPv4 and IPv6 addresses, with their uplink, lead the card.
Every other check is a tile with a plain-word status, the raw setting
and, when it fails, what is wrong. The header counts passing or failing
checks.
A full-access token could create a user or reset a password, sign in as
that user and so reach backups and two-step sign-in settings. Users,
passwords, API tokens and the sign-in rules in PATCH /settings now need
a signed-in user again. /auth/me no longer returns tokenId, and
/settings no longer returns adminUsername.
Bitwarden moves elements around in <body>. A moved dialog stayed open but
fell out of the top layer to the bottom of the page, so a confirmation
seemed to vanish and its checkbox stayed ticked unsaved.
Every argon2 run takes 64 MiB and nothing limited how many ran at once,
so parallel sign-in attempts could run the server out of memory (8 at
once used about 600 MB). At most two now run at once; at most 16
sign-ins wait for one, more get HTTP 429. 30 parallel sign-ins peaked
at 275 MB.
A sign-in attempt now counts toward the lockout before its password is
checked, so parallel attempts cannot get past it; a right password
takes its own attempt back. IPv6 addresses are locked out by /64.
With net.ipv6.conf.all.forwarding=1, Linux ignores router announcements
unless accept_ra is 2, so a server that gets its IPv6 route by SLAAC
(e.g. a Raspberry Pi at home) lost IPv6 once the route expired.
The sysctl file now also sets accept_ra=2 for the default and for every
network card and the IPv6 default-route interface, except where
accept_ra is 0. "update" rewrites the file, which fixes existing
installs. A new health check warns while the uplink still has
accept_ra=1.