Your client still connects on the day the retirement ticket opens. A profile sits in the app, it negotiates, and the tunnel comes up as if nothing changed. That is the moment to work in the opposite direction from how you built the service: start at the client list, walk outward to the listener, then to the firewall, and finish with the credentials that outlive all of them. Shutting down the VM is the last step, not the first. This walkthrough assumes you have console access to the host and the authority to edit client records and DNS.

Walk the client list first

Open whatever distributes your profiles and read the list before you touch the server. Every entry is a device that believes it has access: phones, laptops, a branch router, a container that syncs nightly. Export that list into the ticket, because once the host is gone there is no wg show to tell you who was connected.

Sort each device into replacement endpoint, no longer needed, or unknown. The unknown bucket is the one that matters, since those are the profiles someone will find in a drawer months from now and try. Send the notice on the same channel you use for access requests, and put a date in it for when the old endpoint stops answering.

Revoke the identity, not just the file

Two different actions both get called revocation. On WireGuard, a peer is authorized only by its public key appearing in a peer section with an AllowedIPs entry, so deleting the config file from the laptop changes nothing on the server. Remove the peer block and re-apply the file without dropping the peers you are keeping, as the wg(8) manual describes:

wg show
wg set wg0 peer <CLIENT_PUBLIC_KEY> remove
wg syncconf wg0 <(wg-quick strip wg0)

wg syncconf is the part that saves you a maintenance window. It rebuilds the interface from the file in place, so removing one peer does not tear down the tunnel everyone else is using. If you are retiring the whole host rather than one peer, the order still matters, because those keys may be trusted somewhere else.

With an X.509 setup the revoking step is the certificate, not the file: openssl ca -revoke client.crt and then openssl ca -gencrl -out crl.pem. That only has an effect where the verifier reads it, which means an OpenVPN server with crl-verify configured and reloaded afterwards. If this gateway is the only consumer of that CA, the CRL stops mattering the day it dies; if the same CA signs for a second gateway, a certificate you never revoked keeps working there.

Accounts are the third case and the one most often skipped. When the gateway authenticated against RADIUS, LDAP or a hosted identity provider, the account lives outside the VPN. Disable it in the directory. Deleting a user stanza on the server leaves a valid credential that the replacement endpoint will accept the moment it points at the same directory.

Close the listeners and the paths to them

Stop the service in a way that survives a reboot, then confirm with a socket listing rather than the unit state. systemctl disable --now wg-quick@wg0 stops the interface and removes the routes and rules wg-quick added; systemctl disable --now openvpn-server@server does the same for a templated OpenVPN instance. Follow either one with ss -lunp and ss -ltnp. An empty result for the old port is the only proof that counts.

What wg-quick added, wg-quick removes. What you added by hand stays: a persistent NAT rule, a firewall allow, a router port-forward, a cloud security-group ingress rule, a load-balancer listener. Those need explicit removal, and so does the DNS record, meaning the A, the AAAA and any CNAME pointing at the name. A name that resolves to a dead host fails differently from a name that does not resolve, and that difference will cost someone an afternoon.

The copy you forget is the clone. A snapshot or a VM restored from backup still holds the host keys and the old peer list, and if anything brings it back on the same address, clients reconnect to a service you believe is retired. Label the backup set as belonging to a decommissioned service and keep it out of automatic restore paths.

Chase what still points at the old endpoint

Cached profiles outlive DNS. A saved config with a numeric address keeps dialling after the record is gone, and an always-on client retries on every network change. That is noise rather than access, because the listener is closed, but knowing which one you are looking at decides how you answer the ticket.

Then sweep the dependencies nobody files under VPN: uptime checks probing the port, backup jobs that pulled configs from the host, automation credentials used to push peer entries, recovery accounts created for this endpoint alone. Each is an access path with its own lifetime and its own owner, so each one gets a decision instead of a silent deletion.

Verify, then write it down

Retirement is finished when a test from outside fails the way you expect. From a network you do not control, connect to the old address and port. A filtered or refused result is the success case. In the client app, confirm the profile was removed rather than switched off, and that no management profile quietly re-adds it.

Then record what you did: endpoint, date, keys and certificates revoked, accounts disabled, where the backups live. Whoever finds an old config next needs to answer whether it was revoked or just deleted, without guessing, and a short table in the ticket does that.

Before closing the ticket:

  • client profiles removed or replaced, unknown devices resolved
  • peers removed and the config re-applied, or certificates revoked and the CRL published
  • directory accounts disabled, not only local files deleted
  • service disabled, no listener left on the old port
  • firewall, port forward, security group and DNS entries removed
  • backups labelled, snapshot excluded from restore paths
  • monitoring and automation credentials retired

Verify the replacement separately, from a client that never held the old profile. That single test tells you whether you retired a service or only renamed the problem.

ХӀокхунах тера йаззамаш