Comparing In‑Network Penetration Solutions: frp, WireGuard, and Tailscale in Practice
The article compares three internal‑network penetration methods—frp, WireGuard, and Tailscale—by outlining their architectures, deployment steps on Linux and Windows, configuration details, troubleshooting tips, performance measurements, and recommending suitable scenarios for individuals, teams, and enterprises.
Background and Use Cases
Operations engineers often need to expose local services to the Internet, access cloud servers behind NAT from home, remotely manage Raspberry Pi or NAS devices, or enable communication between servers that are not on the same LAN. All these scenarios require an internal‑network penetration technique.
The core problem is how to reach devices behind NAT. Three common approaches are:
Port mapping : configure port forwarding on the outbound router (requires router control).
Reverse proxy : forward traffic through a public‑IP relay server (e.g., frp, ngrok).
VPN tunnel : build an encrypted virtual LAN (e.g., WireGuard, Tailscale).
frp – Fast Reverse Proxy
How It Works
frp follows a classic client‑server model. A public‑IP server runs frps as the relay, while each internal device runs frpc to connect to the server.
[Internal Device] --frpc--> [Public Server frps] ---> [External User]
<--frpc---- <------Advantages: fully controllable, no third‑party dependency, traffic passes through the relay. Drawbacks: bandwidth and latency are limited by the relay.
Server Deployment (Linux)
# 1. Download frp (server is frps, client is frpc)
# releases page: https://github.com/fatedier/frp/releases
wget https://github.com/fatedier/frp/releases/download/v0.60.0/frp_0.60.0_linux_amd64.tar.gz
tar -xzf frp_0.60.0_linux_amd64.tar.gz
cd frp_0.60.0_linux_amd64
# 2. Create frps.ini
cat > frps.ini <<'EOF'
[common]
bind_addr = 0.0.0.0
bind_port = 7000
token = your-secret-token-here
vhost_http_port = 80
vhost_https_port = 443
dashboard_addr = 0.0.0.0
dashboard_port = 7500
dashboard_user = admin
dashboard_pwd = your-dashboard-password
log_file = /var/log/frps.log
log_level = info
log_max_days = 3
EOF
# 3. Create systemd service
cat > /etc/systemd/system/frps.service <<'EOF'
[Unit]
Description=Frp Server Service
After=network.target
[Service]
Type=simple
User=root
ExecStart=/opt/frp/frps -c /opt/frp/frps.ini
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
# 4. Install and start
mkdir -p /opt/frp
cp frps frps.ini /opt/frp/
systemctl daemon-reload
systemctl enable frps
systemctl start frps
# 5. Verify
systemctl status frps
netstat -tlnp | grep frpsClient Deployment (Linux)
# 1. Download frpc
wget https://github.com/fatedier/frp/releases/download/v0.60.0/frp_0.60.0_linux_amd64.tar.gz
tar -xzf frp_0.60.0_linux_amd64.tar.gz
cd frp_0.60.0_linux_amd64
# 2. Create frpc.ini
cat > frpc.ini <<'EOF'
[common]
server_addr = your-frps-ip
server_port = 7000
token = your-secret-token-here
log_file = /var/log/frpc.log
log_level = info
log_max_days = 3
# SSH example
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 2222
# Web example
[web]
type = http
local_ip = 127.0.0.1
local_port = 8080
custom_domains = your-domain.com
# MySQL example
[mysql]
type = tcp
local_ip = 127.0.0.1
local_port = 3306
remote_port = 13306
EOF
# 3. Create systemd service
cat > /etc/systemd/system/frpc.service <<'EOF'
[Unit]
Description=Frp Client Service
After=network.target
[Service]
Type=simple
User=root
ExecStart=/opt/frp/frpc -c /opt/frp/frpc.ini
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
# 4. Install and start
mkdir -p /opt/frp
cp frpc frpc.ini /opt/frp/
systemctl daemon-reload
systemctl enable frpc
systemctl start frpc
# 5. Verify
systemctl status frpcClient Deployment (Windows)
# 1. Download Windows binary from https://github.com/fatedier/frp/releases
# 2. Create frpc.ini (same content as Linux)
# 3. Create start‑frpc.bat
@echo off
cd /d %~dp0
frpc.exe -c frpc.ini
pause
# 4. Optional: register as a Windows service using NSSM or winswConfiguration Details
# frps.ini example
[common]
bind_addr = 0.0.0.0
bind_port = 7000
token = your-secret-token-here
vhost_http_port = 80
vhost_https_port = 443
# optional: bind specific IP, allow_ports, bandwidth limits, heartbeat, logging, etc.
log_file = /var/log/frps.log
log_level = info
log_max_days = 3
# frpc.ini example
[common]
server_addr = 1.2.3.4
server_port = 7000
token = your-secret-token-here
log_file = /var/log/frpc.log
log_level = info
log_max_days = 3
[web]
type = http
local_ip = 127.0.0.1
local_port = 8080
custom_domains = app.your-domain.com
subdomain = app
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 2222
[udp]
type = udp
local_ip = 127.0.0.1
local_port = 25565
remote_port = 25565
[stcp]
type = stcp
local_ip = 127.0.0.1
local_port = 22
sk = your-secret-keyCommon Troubleshooting
# 1. View frps logs
tail -f /var/log/frps.log
# 2. View frpc logs
tail -f /var/log/frpc.log
# 3. Test server port connectivity
nc -zv your-frps-ip 7000
telnet your-frps-ip 7000
# 4. Connection failures – check token consistency, firewall rules, frps status
systemctl status frps
# 5. HTTP proxy not working – verify DNS resolves to frps IP, vhost_http_port matches, custom_domains correct
# 6. Unstable client – adjust heartbeat_interval/heartbeat_timeout in frpc.ini
heartbeat_interval = 5
heartbeat_timeout = 20
# 7. Bandwidth limits – configure bandwidth_limit_type and bandwidth_limit in frps.ini
bandwidth_limit_type = server
bandwidth_limit = 10MBWireGuard – Virtual LAN
How It Works
WireGuard creates a peer‑to‑peer encrypted tunnel, forming a virtual LAN:
[Machine A] <-- WireGuard tunnel --> [Machine B]
10.0.0.1 10.0.0.2
[Machine A] <-- WireGuard tunnel --> [Machine C]
10.0.0.1 10.0.0.3Advantages: encrypted tunnel, direct P2P connection (no relay), high performance. Drawbacks: requires each peer’s public IP and reconfiguration of all nodes when a new device joins.
Server Deployment (Gateway)
# 1. Install WireGuard
apt update && apt install -y wireguard # Ubuntu/Debian
# or for CentOS/RHEL 8+
dnf install -y epel-release && dnf install -y wireguard-tools
# 2. Generate key pairs for server and each client
cd /etc/wireguard
umask 077
wg genkey | tee server_private.key | wg pubkey > server_public.key
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
wg genkey | tee client2_private.key | wg pubkey > client2_public.key
# 3. Create server config (wg0.conf)
cat > wg0.conf <<'EOF'
[Interface]
PrivateKey = <SERVER_PRIVATE_KEY>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o wg0 -j ACCEPT
# Client 1
[Peer]
PublicKey = <CLIENT1_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
# Client 2
[Peer]
PublicKey = <CLIENT2_PUBLIC_KEY>
AllowedIPs = 10.0.0.3/32
EOF
chmod 600 wg0.conf
# Enable IP forwarding
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p
# Start WireGuard
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
# Verify
wg show
ip addr show wg0Client Deployment (Linux)
# 1. Install WireGuard
apt install -y wireguard
# 2. Generate keys
cd /etc/wireguard
umask 077
wg genkey | tee client_private.key | wg pubkey > client_public.key
# 3. Create client config
cat > wg0.conf <<'EOF'
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.0.0.2/24
DNS = 8.8.8.8, 8.8.4.4
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = your-server-ip:51820
PersistentKeepalive = 25
AllowedIPs = 10.0.0.0/24
EOF
chmod 600 wg0.conf
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
# Test connectivity
ping 10.0.0.1
ping 10.0.0.3Client Deployment (Windows/macOS)
# Windows: download installer from https://www.wireguard.com/install/
# macOS: brew install wireguard-tools or download from App Store
# GUI steps:
1. Import or create a tunnel
2. Fill PrivateKey
3. Set Address (e.g., 10.0.0.2/24)
4. Add Peer – Server public key, Endpoint (server-ip:51820)
5. Set AllowedIPs (10.0.0.0/24 for LAN only)
6. Save and activateFull‑Tunnel (All Traffic) Example
# Client config for full VPN
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.0.0.2/24
DNS = 8.8.8.8
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = your-server-ip:51820
PersistentKeepalive = 25
AllowedIPs = 0.0.0.0/0
EOF
# Note: When AllowedIPs is 0.0.0.0/0, all traffic is routed through WireGuard; the server must provide NAT and routing.Common Troubleshooting
# 1. Check status
wg show
wg show wg0
# 2. Verify interface
ip addr show wg0
# 3. Verify routing table
ip route show
# 4. Connection failures – ensure UDP 51820 is open, keys match, AllowedIPs include target IPs
# 5. NAT traversal – symmetric NAT not supported; set PersistentKeepalive = 25 or configure router port forwarding
# 6. Bandwidth test – use iperf3
iperf3 -s # on server
iperf3 -c 10.0.0.1 # on clientTailscale – Managed WireGuard Mesh
How It Works
Tailscale builds on WireGuard but adds automatic key management, NAT traversal, and a control plane. The free tier does not require a public relay server; DERP relays are used when direct P2P fails.
Advantages:
1. No manual key handling – Tailscale manages keys.
2. NAT traversal works in most cases without a relay.
3. Free DERP relays are available.
4. Supports Exit Node, ACLs, and all major platforms.Installation and Deployment
# Linux install (official script)
curl -fsSL https://tailscale.com/install.sh | sh
# or manual RPM for CentOS/RHEL
curl -fsSL https://pkgs.tailscale.com/stable/tailscale-latest.x86_64.rpm -o tailscale.rpm
rpm -i tailscale.rpm
# Start daemon
systemctl start tailscaled
# or run directly
tailscaled &
# Join network (default Tailscale service)
tailscale up
# For custom control plane (e.g., Headscale)
tailscale up --login-server=https://login.example.com
# View status and assigned IPs
tailscale status
tailscale ip
# Enable autostart
systemctl enable tailscaledExit Node Configuration
# On the server that will act as Exit Node
tailscale up --advertise-exit-node
# Approve in Tailscale admin console
# On client devices, select the Exit Node
tailscale up --exit-node=<EXIT_NODE_IP>
# Or allow all traffic via the Exit Node
tailscale up --exit-node=allow-networkingACL (Access Control List) Example
{
"acls": [
{"action": "accept", "src": ["*"], "dst": ["*:*"]},
{"action": "accept", "src": ["group:developers"], "dst": ["tag:production:22"]},
{"action": "accept", "src": ["tag:ci"], "dst": ["tag:production:0-65535"]}
],
"tagOwners": {
"tag:production": ["group:admins"],
"tag:ci": ["group:admins"]
}
}Headscale – Self‑Hosted Control Plane
# Deploy Headscale with Docker Compose
version: '3'
services:
headscale:
image: ghcr.io/juanfont/headscale:latest
container_name: headscale
volumes:
- /etc/headscale:/etc/headscale
- /var/lib/headscale:/var/lib/headscale
ports:
- "8080:8080"
- "3478:3478/udp"
command: serve
restart: unless-stopped
# Create configuration
mkdir -p /etc/headscale /var/lib/headscale
cat > /etc/headscale/config.yaml <<'EOF'
server_url: http://your-headscale-ip:8080
listen_addr: 0.0.0.0:8080
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
prefix: 100.64.0.0/10
derp:
server:
enabled: false
urls:
- https://controlplane.tailscale.com/derpmap/default
EOF
# Initialize and register a node
docker exec -it headscale headscale nodes register --key <NODE_KEY>
# Clients join the private control plane
tailscale up --login-server=http://your-headscale-ip:8080Common Troubleshooting
# 1. Check daemon status
tailscale status
# 2. View logs
journalctl -u tailscaled -f
# 3. Test connectivity to a peer
tailscale ping <PEER_NAME_OR_IP>
# 4. Verify assigned IPs
tailscale ip -4
tailscale ip -6
# 5. Diagnose connectivity issues
systemctl status tailscaled
# 6. If "NeedsLogin", re‑authenticate
tailscale up
# 7. Check NAT traversal
tailscale netcheck
# 8. Force DERP relay
tailscale up --derp=http://custom-derp-server
# 9. Logout and re‑login for multiple devices
tailscale logout
tailscale upFeature Comparison
Feature | frp | WireGuard | Tailscale
-----------------+--------------------+--------------------+-------------------
Architecture | C/S, needs relay | P2P, no relay | Hybrid, relay optional
Bandwidth | Limited by relay | Limited by server | No bottleneck in P2P
Latency | Higher (relay) | Low (direct) | Low (P2P) / higher (DERP)
NAT traversal | Supported | No symmetric NAT | Supported
Config difficulty | Medium | Harder | Easy
Cost | Requires public server | Requires public server | Free tier sufficient
Platforms | Supported | Mainstream OSes | All major platforms
ACL | No | No | Yes
Key management | Manual | Manual | Automatic
Third‑party deps | None | None | Tailscale service (optional self‑host)Recommended Scenarios
frp suitable when:
- Temporary exposure of local services (webhooks, debugging)
- You control a public relay server
- Only simple TCP/UDP port forwarding is needed
- Traffic volume is modest
WireGuard suitable when:
- Stable long‑lived connections are required
- Multiple servers need to form a private network
- Servers have public IPs
- Network environment is simple (no symmetric NAT)
- High performance is a priority
Tailscale suitable when:
- You prefer not to manage servers
- Devices span many platforms (Windows, macOS, Linux, iOS, Android)
- ACL‑based access control is needed
- NAT or symmetric NAT is present
- Quick, hassle‑free deployment is desiredBandwidth and Latency Benchmarks
Test environment: client behind NAT, server on Alibaba Cloud South China (Guangzhou).
frp:
- Latency: 80‑120 ms (relay)
- Bandwidth: 30‑50 MB/s per connection (relay limited)
- Suitable for web services, SSH, RDP
WireGuard (P2P):
- Latency: 40‑60 ms (direct)
- Bandwidth: ~100 MB/s+ (close to server limit)
- Suitable for file transfer, database connections, SSH
Tailscale (P2P):
- Latency: 40‑60 ms (same as WireGuard)
- Bandwidth: similar to WireGuard
- Relay mode (DERP): 80‑120 ms
- Suitable for all scenariosDeployment Recommendations
Small Teams / Individual Use
Recommended: Tailscale free tier
Steps:
1. Register a Tailscale account (GitHub/Google login)
2. Install the client on each device
3. Follow the on‑screen authentication
4. Start using the mesh networkMedium‑Size Teams
Recommended: Hybrid WireGuard + Tailscale
Steps:
1. Acquire a public‑IP server
2. Deploy WireGuard on the server
3. Install Tailscale on team devices
4. Configure ACL rules in Tailscale
5. WireGuard interconnects serversEnterprise Use
Recommended: Self‑hosted Headscale + WireGuard
Steps:
1. Deploy Headscale control plane (Docker or VM)
2. Install WireGuard on servers and register nodes via Headscale
3. Define enterprise‑grade ACL policies in Headscale
4. Roll out clients using the private control planeFAQ
frp
Q: frp connects but the service is unreachable? Check that the client’s local_port matches the service, the server’s remote_port matches the client config, the service is listening locally, and firewalls allow the ports.
Q: How to enable HTTPS with frp? Either configure TLS termination on frps with a certificate, or use the https2http plugin on frpc to forward HTTPS to a local HTTP service.
Q: How to limit bandwidth? Set bandwidth_limit_type and bandwidth_limit in frps.ini.
WireGuard
Q: Connection succeeds but ping fails? Verify that IP forwarding is enabled on the server, firewall rules allow the wg0 interface, and the client’s AllowedIPs includes the target address.
Q: How to add a new client? Generate a key pair on the client, send the public key to the administrator, add a [Peer] section with the public key and AllowedIPs to wg0.conf, then restart the WireGuard service.
Q: Does WireGuard support Windows/macOS? Yes. Windows installers are available from the official site; macOS users can install via Homebrew or the App Store.
Tailscale
Q: Does the free tier have traffic limits? No overall traffic limit, but DERP relays are capped at ~1 Mbps per connection. Direct P2P connections are unlimited.
Q: How to disable DERP and force P2P? Run tailscale netcheck to see NAT type. If the NAT is symmetric, P2P may not work and DERP is required.
Q: Can I self‑host the control plane? Yes. Headscale includes a built‑in DERP server, or you can deploy a separate derper instance.
Q: Is Tailscale secure? Tailscale uses WireGuard’s cryptography; the traffic is end‑to‑end encrypted and the Tailscale service does not decrypt user data.
Conclusion
All three internal‑network penetration solutions have distinct trade‑offs:
frp – Traditional reverse‑proxy approach; fully controllable but requires a public relay and incurs bandwidth/latency limits.
WireGuard – High‑performance VPN; ideal for stable, high‑throughput private networks, but needs public IPs and manual key management.
Tailscale – Managed WireGuard mesh; fastest to set up, handles NAT traversal automatically, and offers ACLs, making it the most convenient for personal use and small teams.
Choosing the right tool depends on scale, performance requirements, and operational overhead.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
MaGe Linux Operations
Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
