Guide

Your Phone and a First Private Service

Part 4 of 5By Amith Kumar7 min read
Part 4 of 5WireGuard VPN and Private Networking: A Hands-On Guide
  1. 1Your Own WireGuard VPN: What You Will Build
  2. 2Install and Configure the WireGuard Server
  3. 3Connect Your Laptop to WireGuard
  4. 4Your Phone and a First Private Service
  5. 5Keep It Running and Add More Devices
Verified 22 Jul 2026 · Ubuntu 24.04 LTS

Reach a private service from your phone

This is the payoff. You add your phone to the VPN with a QR scan, stand up a small private service on the slice that listens only on the tunnel, and reach it from the phone over WireGuard. The service answers on 10.99.0.1 and is refused on the public IP, so it exists only for connected devices.

Reaching a private service this way, from a phone, over a link no one else can see, is the point of the whole build on Ubuntu 24.04. You also meet the one firewall pitfall that blocks almost everyone here.

Key takeaways
  • qrencode turns a phone config into a QR the WireGuard app scans in one step.
  • A private service binds to the tunnel address 10.99.0.1, never to the public IP.
  • The pitfall: ping works but the service is blocked until you allow its port on wg0.
  • Fetching the same page on the public IP is refused, which proves it is private.

Add your phone and build its config

Register the phone on the server the same way you did the laptop, giving it the next free address, 10.99.0.3.

sudo wg set wg0 peer UIoYdw35eEBudb6vPtipfjXlWYEafMgyseC0kWyrix8= allowed-ips 10.99.0.3/32

Then write a phone.conf for it. It matches the laptop config but adds a DNS line so name lookups on the phone resolve through the tunnel while it is connected.

[Interface]
PrivateKey = <contents of phone_private.key>
Address = 10.99.0.3/24
DNS = 10.99.0.1

[Peer]
PublicKey = pvrDkgmOe3T2dotd5eA3M9oWtfc7cQJ1k2l40JMunxo=
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.99.0.0/24
PersistentKeepalive = 25

Turn the config into a QR

Typing a config on a phone is painful, so WireGuard's phone apps import from a QR. Render phone.conf as a QR right in the terminal with qrencode.

qrencode -t ansiutf8 < phone.conf
qrencode rendering the phone WireGuard config as a scannable QR code in the terminal, ready for the phone app to import the tunnel with one scan

Install the WireGuard app from the Play Store on Android or the App Store on iOS, choose "Add tunnel", then "Scan from QR code", and point the camera at the terminal. The tunnel imports in one step, with no typing.

Checkpoint

After scanning, toggle the tunnel on in the phone app. It should flip to a connected state, and sudo wg show wg0 on the server should list the phone's peer with a fresh handshake. Your phone is now on the VPN.

Stand up a private service on the tunnel

Now give the phone something private to reach. Serve a small page bound to the tunnel address only, so it never listens on the public interface. A one-line Python server is enough for the demo.

sudo python3 -m http.server 8080 --bind 10.99.0.1 --directory /opt/wg-private

The --bind 10.99.0.1 is the important part: the service accepts connections on the tunnel address alone, not on the slice's public IP. The page it serves says, plainly, You reached this over the WireGuard tunnel, and that it is served only on 10.99.0.1. In a real setup this stands in for an admin panel, a database UI, or a home dashboard you never want on the open internet.

The firewall pitfall: ping works, the service does not

Here is the pitfall to expect. With the tunnel up, ping the server and it answers, so the link looks fine. But fetch the service and it hangs.

curl -m 5 http://10.99.0.1:8080
curl to the private service on the tunnel IP timing out after five seconds, the firewall pitfall: ping works but the service is blocked until the tunnel is allowed to the port
Expect this to fail

The request times out after five seconds, even though ping to 10.99.0.1 succeeds. This is the real trap: the firewall permits ICMP but has no rule for the new TCP port arriving on the tunnel interface, so the service is silently blocked. It is not a WireGuard fault.

The tunnel is carrying packets, proven by ping, but the host firewall drops the connection to port 8080 on the wg0 interface. Ping uses ICMP, which ufw allows by default, while your new service is a TCP port that nothing has opened yet.

Open the tunnel to the service port

The fix is one rule: allow inbound traffic on the wg0 interface to the service port. This keeps the port open only on the tunnel, never on the public side.

sudo ufw allow in on wg0 to any port 8080 proto tcp
ufw allow in on wg0 adding the rule that lets tunnel traffic reach TCP port 8080, the fix for the blocked service

Scoping the rule to on wg0 matters. It permits the service for connected VPN peers and no one else, so the public interface stays closed. Reach for this pattern for every private service you add later.

Checkpoint

sudo ufw status should now show 8080/tcp on wg0 ALLOW. The service is reachable to tunnel peers and still invisible on the public IP.

Reach the private page

Fetch the service again over the tunnel, this time asking curl to show the response headers.

curl -i http://10.99.0.1:8080
curl over the tunnel returning HTTP 200 and the private page, served only on 10.99.0.1 and reached from the connected phone
The private page loaded in a browser through the WireGuard tunnel, a service the public internet cannot reach, opened from the connected device
Checkpoint

You get HTTP/1.1 200 OK and the page body in well under a second. Open http://10.99.0.1:8080 in the browser on your connected phone or laptop and the same private page loads. You have reached something private, from your phone, that the public internet cannot see.

Prove it is private

The claim is that this page exists only on the tunnel. Test it by asking for the same port on the slice's public IP, from outside the VPN.

curl -m 5 http://203.0.113.10:8080
curl to the slice public IP on port 8080 refused with connection failed, proof the service is bound to the tunnel only and the public internet cannot see it

The connection is refused with no response at all. Because the service bound to 10.99.0.1 and the firewall only opened the port on wg0, there is nothing listening for the public internet to reach. Ping proved the tunnel; this refusal proves the privacy.

Frequently asked questions

Why did ping work but the service time out?

ufw permits ICMP echo by default, so ping to the tunnel address answers as soon as the tunnel is up. Your new service is a TCP port, and nothing has opened it, so the firewall drops the connection on the wg0 interface. Adding ufw allow in on wg0 to any port 8080 proto tcp opens it for tunnel peers only, which fixes the hang.

How does the phone import a config from a QR?

The WireGuard apps for Android and iOS read a QR that encodes the full config text. You run qrencode -t ansiutf8 < phone.conf, choose "Scan from QR code" in the app, and point the camera at the terminal. The tunnel imports with its keys and peer in one step, avoiding error-prone typing of a private key on a phone keyboard.

Why bind the service to 10.99.0.1 instead of all interfaces?

Binding to the tunnel address means the service accepts connections only from inside the VPN. If you bound it to 0.0.0.0, it would also listen on the public IP, and only the firewall would stand between it and the internet. Binding to 10.99.0.1 plus the wg0-scoped firewall rule gives two independent reasons it stays private.

Can I reach the service by a name instead of an IP?

Yes. Because the phone config sets DNS = 10.99.0.1, you can run a small resolver on the server that maps a name like private.internal to the tunnel address, or add a hosts entry on each device. The service itself does not change; you are only giving 10.99.0.1 a friendlier name for the browser.

Recap and what is next

You added your phone with a QR scan, stood up a service bound to 10.99.0.1, hit the firewall pitfall where ping works but the service is blocked, fixed it with a single wg0-scoped ufw rule, and loaded the private page from the connected device. The refused fetch on the public IP proved the service is private. That is a working personal VPN reaching a real private service.

The final chapter makes it durable: the tunnel survives reboots under systemd, a preshared key adds a layer, you add more devices as a hub-and-spoke, and a deep-dive tail covers full-tunnel routing, NAT and DNS.

Reach your own private services

A slice gives your phone a fixed public IP to dial and a place to host services no one else can see. Launch a slice and run your own WireGuard VPN, then put an admin page or dashboard behind the tunnel.


Run it on your own slice

Launch a slice and run your own WireGuard VPN

WireGuard needs one always-on box with a public IP for your devices to dial. Spin up a slice, follow this series from the bare server to a private service you reach from anywhere, and hold the keys yourself.

Get early access

This guide is provided for educational purposes and offered as is, without warranty of any kind. The commands change the configuration of a server you control, and some can lock you out if run out of order. Test on a non-production machine, keep a second session open, and take a snapshot or backup before you begin. You are responsible for changes made to your own systems; ServerCake accepts no liability for any loss, downtime, lockout, or damage arising from following this guide or running the accompanying script.

Was this guide helpful?
Share

Spotted something out of date or have a question?

Built by the ServerCake team

Cloud that speaks India.

ServerCake is India's KYC-verified cloud: VMs and managed databases priced in ₹, billed with GST, on India-resident infrastructure. Reserve your spot for early access.

Reserve your spot