VPS Tutorials
Set Up FRP Intranet Tunneling on a VPS (2026): frps/frpc, Domain HTTPS, SSH and Security Configuration
This tutorial shows how to deploy FRP intranet tunneling on an Ubuntu 24.04 VPS, with complete frps/frpc configuration, Token file authentication, enforced TLS, port allowlists, SSH remote access, NAS domain HTTPS, systemd autostart, traffic monitoring, upgrade and rollback, and common troubleshooting.

Your home NAS, development machine or small server has no public IPv4, and the ISP may even use CGNAT. In that case, even if you configure port forwarding on the router, external networks may not be able to connect back. FRP's approach is: run frps on a VPS with a public IP, have the intranet device actively run frpc to connect to the VPS, and then let the VPS forward external requests back to the intranet service.
This is also a scenario closely tied to VPS purchasing. FRP itself is lightweight, but public IPv4, network latency, port policy, bandwidth and monthly traffic all directly affect the experience. This article uses an Ubuntu 24.04 VPS and Linux intranet devices as examples, with the current official binaries, TOML configuration, Token files and systemd, to complete SSH, web services, HTTPS, secure access, updates and troubleshooting.
The official FRP documentation defines it as a high-performance reverse proxy focused on intranet tunneling, consisting of frps on a public server and frpc on the intranet device. It supports TCP, UDP, HTTP, HTTPS, STCP, XTCP and port multiplexing modes.
| Solution | Best for | Requires a public VPS | Default access scope | Main limitations |
|---|---|---|---|---|
| FRP | Mapping SSH, NAS, web and TCP/UDP services to the public internet | Yes | Can be public or private access | Traffic usually passes through the VPS, and you must harden it yourself |
| WireGuard | Your own phone and computer securely entering the home intranet | Usually required | VPN members | Inconvenient for sharing publicly with ordinary visitors |
| Tailscale | Private networking and identity management across multiple devices | Not necessarily | Account-authorized devices | Depends on a coordination service; public services need an additional solution |
| Cloudflare Tunnel | Publishing HTTP/HTTPS applications without opening inbound ports | No | Web access | Not suitable for arbitrary public TCP/UDP scenarios |
| SSH reverse tunnel | Temporarily mapping a single TCP service | Yes | Per SSH configuration | Limited multi-service management and monitoring |
If you only need to access the NAS from your own phone or laptop, prefer WireGuard VPN, which usually has a smaller attack surface. When you need to provide public domains, fixed TCP ports or UDP services to different users, FRP is more flexible.
There is also an easily confused point to understand: data for ordinary TCP, UDP and HTTP mappings passes through the VPS; XTCP attempts to make the two endpoints connect peer-to-peer, but not all NAT environments succeed. Do not treat FRP as magic that makes "traffic never pass through the server".
The FRP control process usually does not use much CPU or memory; what really matters is public IP, network route and traffic.
| Use case | Recommended starting configuration | Bandwidth and traffic focus |
|---|---|---|
| Forwarding only SSH and remote management | 1 vCPU, 512MB–1GB memory, 10GB disk | 10Mbps and a few hundred GB per month are usually enough |
| NAS admin page, photo albums and a small number of files | 1–2 vCPU, 1GB memory, 20GB disk | 100Mbps recommended, at least 1TB monthly traffic |
| Video streaming and large file downloads | 2 vCPU, 1–2GB memory | High bandwidth, large traffic or unlimited-traffic terms matter most |
| Shared by multiple homes or teams | 2 vCPU, 2GB memory | Separate Token/OIDC, port allowlists, monitoring |
Continuously using 10Mbps gives a theoretical monthly transfer of about 3.24TB, while continuously using 100Mbps gives about 32.4TB. In practice it will not run at full speed all the time, but this shows why "1Gbps port" does not mean "suitable for long-term relaying of large files". Before purchasing, you must confirm:
- Whether the plan is billed by outbound traffic, bidirectional traffic or a fair use policy;
- Whether exceeding the limit results in throttling, suspension or pay-as-you-go charges;
- Whether a dedicated public IPv4 is provided and whether custom TCP/UDP ports are allowed;
- The latency, packet loss and peak-hour performance from the data center to main visitors and home broadband;
- Whether the provider allows reverse proxying, port forwarding and long-term high-traffic relaying.
FRP's access path is roughly "user → VPS → home", so a farther VPS data center is not necessarily better. When the main users are in mainland China and the VPS is in a distant data center, the latency and packet loss of the two network segments add up. After going live, it is recommended to use VPS traffic monitoring and overage alerts to keep observing, rather than waiting for a bill or suspension notice.
This article uses the following ports:
| Port | Protocol | Purpose | Public rule |
|---|---|---|---|
22 | TCP | The VPS's own SSH | Better to allow only the administrator IP |
7000 | TCP | Control port for frpc connecting to frps | Must be open; restrict the source as much as possible |
6000 | TCP | Example: forwarding intranet SSH | Allow only the administrator IP |
8080 | TCP | Example: NAS web to the VPS local machine | Not open to the public internet; accessed locally by Caddy |
80/443 | TCP | Caddy domain and HTTPS | Open when publishing web services |
7500 | TCP | Optional Dashboard | Not open to the public internet; listen only on 127.0.0.1 |
Port numbers can be changed, but the server configuration, client configuration, UFW and cloud security group must be consistent. Do not open all of 1-65535, and do not allow clients that obtain the Token to occupy server ports arbitrarily.
Log in to the VPS, update the system and install download, verification and firewall tools:
sudo apt update
sudo apt upgrade -y
sudo apt install -y curl ca-certificates tar openssl ufw
Create a dedicated service user without a login shell:
sudo useradd --system \
--home-dir /var/lib/frp \
--create-home \
--shell /usr/sbin/nologin \
frp
sudo install -d -m 750 -o root -g frp /etc/frp
sudo install -d -m 750 -o frp -g frp /var/lib/frp
id frp
Do not run frps as root. FRP only needs to listen on 7000 and the high proxy ports allowed in this article; domain HTTPS is handled by Caddy listening on 80/443, so frps does not need low-port privileges.
As of August 2026, the current version in the official release channel is v0.71.0. Minor versions will continue to be updated; before deployment, check the official FRP Releases again, and do not download installation packages from unknown one-click scripts or cloud drives.
On an x86_64 VPS, run:
FRP_VERSION=0.71.0
FRP_ARCH=amd64
FRP_PACKAGE="frp_${FRP_VERSION}_linux_${FRP_ARCH}.tar.gz"
cd /tmp
curl -fLO \
"https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_PACKAGE}"
curl -fLO \
"https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_sha256_checksums.txt"
grep " ${FRP_PACKAGE}$" frp_sha256_checksums.txt | sha256sum -c -
tar -xzf "$FRP_PACKAGE"
sudo install -m 755 \
"frp_${FRP_VERSION}_linux_${FRP_ARCH}/frps" \
/usr/local/bin/frps
/usr/local/bin/frps --version
sha256sum must return OK. If no filename matches or verification fails, stop the installation first, recheck the version, architecture and download source, and do not skip verification and continue.
On an ARM64 VPS, change FRP_ARCH to arm64. You can first run:
uname -m
x86_64 corresponds to amd64, and aarch64 or arm64 corresponds to arm64.
Since v0.64.0, FRP supports reading the Token from a file, avoiding writing the secret directly into TOML. Generate a 32-byte random Token:
sudo sh -c 'umask 027; openssl rand -hex 32 > /etc/frp/server_token'
sudo chown root:frp /etc/frp/server_token
sudo chmod 640 /etc/frp/server_token
sudo wc -c /etc/frp/server_token
The file length should be 65 bytes: 64 hexadecimal characters plus a newline. Do not send the Token to group chats, tickets, screenshots or Git repositories.
Create /etc/frp/frps.toml:
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/etc/frp/server_token"
transport.tls.force = true
allowPorts = [
{ single = 6000 },
{ single = 8080 },
{ start = 6100, end = 6199 }
]
maxPortsPerClient = 10
log.to = "console"
log.level = "info"
log.disablePrintColor = true
Four layers of constraints are applied here:
- The Token verifies whether frpc is authorized to connect;
transport.tls.force = truerejects clients that have not enabled TLS;allowPortslimits the remote ports clients can request;maxPortsPerClientprevents a single client from creating unlimited proxies.
The official authentication documentation states that auth.token and auth.tokenSource cannot be used at the same time. After configuring the file source, do not add a plaintext auth.token as well.
After saving the configuration, set permissions and validate the syntax:
sudo chown root:frp /etc/frp/frps.toml
sudo chmod 640 /etc/frp/frps.toml
sudo -u frp /usr/local/bin/frps verify -c /etc/frp/frps.toml
The official documentation supports using frps verify and frpc verify to check TOML before starting. Do not continue using the INI format from old tutorials; INI is deprecated, and new features only support TOML, YAML or JSON.
Create /etc/systemd/system/frps.service:
[Unit]
Description=FRP server
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=frp
Group=frp
WorkingDirectory=/var/lib/frp
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/frp
[Install]
WantedBy=multi-user.target
Enable and check:
sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo systemctl status frps --no-pager
sudo journalctl -u frps -n 100 --no-pager
sudo ss -lntp | grep ':7000'
This approach is consistent with the official FRP systemd guide, with the addition of a dedicated user and systemd sandbox restrictions. Under ProtectSystem=strict, if you later write logs to files, you need to create a writable directory separately and add it to ReadWritePaths; this article sends logs directly to journald.
First confirm the SSH rule, then open the control port:
sudo ufw allow OpenSSH
sudo ufw allow 7000/tcp comment 'FRP control'
sudo ufw enable
sudo ufw status numbered
If the intranet egress has a fixed public IP, for example 198.51.100.20, the control port can be further restricted by source:
sudo ufw delete allow 7000/tcp
sudo ufw allow from 198.51.100.20 to any port 7000 proto tcp
When the home broadband public address changes frequently, a fixed source rule cannot be used long term, so you must rely on a strong random Token, TLS, port allowlists and log monitoring.
The cloud provider security group must also have corresponding rules. UFW and the security group are two layers of firewall; when the process is listening normally but external connections fail, check with the port, firewall and security group troubleshooting checklist.
The following command selects the installation package according to common Linux architectures:
FRP_VERSION=0.71.0
case "$(uname -m)" in
x86_64) FRP_ARCH=amd64 ;;
aarch64|arm64) FRP_ARCH=arm64 ;;
armv7l) FRP_ARCH=arm ;;
*) echo "Unsupported architecture: $(uname -m)"; exit 1 ;;
esac
FRP_PACKAGE="frp_${FRP_VERSION}_linux_${FRP_ARCH}.tar.gz"
cd /tmp
curl -fLO \
"https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_PACKAGE}"
curl -fLO \
"https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_sha256_checksums.txt"
grep " ${FRP_PACKAGE}$" frp_sha256_checksums.txt | sha256sum -c -
tar -xzf "$FRP_PACKAGE"
sudo install -m 755 \
"frp_${FRP_VERSION}_linux_${FRP_ARCH}/frpc" \
/usr/local/bin/frpc
Create the client user and directory:
sudo useradd --system \
--home-dir /var/lib/frp \
--create-home \
--shell /usr/sbin/nologin \
frp 2>/dev/null || true
sudo install -d -m 750 -o root -g frp /etc/frp
sudo install -m 640 -o root -g frp /dev/null /etc/frp/client_token
sudo nano /etc/frp/client_token
Copy the contents of /etc/frp/server_token on the VPS into it through a trusted SSH session or password manager, then save and check permissions:
sudo chown root:frp /etc/frp/client_token
sudo chmod 640 /etc/frp/client_token
sudo wc -c /etc/frp/client_token
The server and client Tokens must be exactly the same, including not accidentally adding spaces. FRP automatically strips leading and trailing whitespace from the file, but you still should not write comments or extra lines in the Token file.
Assume the VPS public address uses the documentation example IP 203.0.113.10. Create /etc/frp/frpc.toml:
serverAddr = "203.0.113.10"
serverPort = 7000
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/etc/frp/client_token"
transport.tls.enable = true
[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
transport.bandwidthLimit = "10MB"
transport.bandwidthLimitMode = "server"
Replace 203.0.113.10 with the VPS's real public IP. Validate the configuration and test in the foreground:
sudo chown root:frp /etc/frp/frpc.toml
sudo chmod 640 /etc/frp/frpc.toml
sudo -u frp /usr/local/bin/frpc verify -c /etc/frp/frpc.toml
sudo -u frp /usr/local/bin/frpc -c /etc/frp/frpc.toml
After seeing the proxy register successfully, check the port on the VPS:
sudo ss -lntp | grep ':6000'
Allow only the administrator's fixed IP to access the example SSH mapping:
sudo ufw allow from 198.51.100.30 to any port 6000 proto tcp
Then test from that administrator network:
ssh -p 6000 [email protected]
Replace the user, VPS IP and administrator source IP. The FRP Token only authenticates frpc and cannot replace the intranet SSH key authentication. Intranet devices should still disable weak passwords, restrict users and retain SSH's own security policies.
On the intranet Linux device, create /etc/systemd/system/frpc.service:
[Unit]
Description=FRP client
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=frp
Group=frp
WorkingDirectory=/var/lib/frp
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/frp
[Install]
WantedBy=multi-user.target
Start and view logs:
sudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo systemctl status frpc --no-pager
sudo journalctl -u frpc -n 100 --no-pager
If intranet SSH only listens on a certain LAN IP and not on 127.0.0.1:22, change localIP to the real listening address. First run ss -lntp and ssh localhost on the intranet device to confirm the local service is available, then troubleshoot FRP.
Mapping the NAS admin panel directly to a public high port is simple, but lacks HTTPS, access control and unified logs. A more robust structure is:
访问者 -> HTTPS 443 -> VPS Caddy -> 127.0.0.1:8080 -> frps -> frpc -> NAS 127.0.0.1:5000
Add the following to the end of /etc/frp/frpc.toml on the intranet device:
[[proxies]]
name = "nas-web"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5000
remotePort = 8080
transport.bandwidthLimit = "20MB"
transport.bandwidthLimitMode = "server"
After validation, restart the client:
sudo -u frp /usr/local/bin/frpc verify -c /etc/frp/frpc.toml
sudo systemctl restart frpc
sudo journalctl -u frpc -n 50 --no-pager
On the VPS, confirm that the local machine can access the mapped port, but do not open the UFW rule for 8080:
curl -I http://127.0.0.1:8080
sudo ufw status numbered
Point the A record of nas.example.com to the VPS, then install and configure Caddy:
nas.example.com {
reverse_proxy 127.0.0.1:8080
}
Open the web port:
sudo ufw allow 80/tcp comment 'HTTP for Caddy'
sudo ufw allow 443/tcp comment 'HTTPS for Caddy'
For Caddy's official repository installation, DNS, automatic certificates and log configuration, see the site's Caddy reverse proxy tutorial.
The HTTPS here only protects the "visitor to VPS" segment; traffic from the VPS to the intranet service still passes through the FRP channel. FRP has enabled TLS encryption by default since v0.50.0, but the default generated certificate does not let frpc verify the identity of frps. For highly sensitive services, you should further configure your own CA and certificates for one-way or mutual verification, rather than relying only on default encryption.
Databases, NAS admin panels, remote desktops and home router admin panels are not suitable for direct exposure to the whole internet. FRP's STCP mode requires the visitor side to also run frpc, and the VPS does not listen on the business port for the public internet.
On the accessed intranet device, add:
[[proxies]]
name = "secret-nas-admin"
type = "stcp"
secretKey = "use-a-separate-random-secret"
localIP = "127.0.0.1"
localPort = 5000
Visitor device configuration:
serverAddr = "203.0.113.10"
serverPort = 7000
auth.method = "token"
auth.tokenSource.type = "file"
auth.tokenSource.file.path = "/etc/frp/client_token"
[[visitors]]
name = "secret-nas-admin-visitor"
type = "stcp"
serverName = "secret-nas-admin"
secretKey = "use-a-separate-random-secret"
bindAddr = "127.0.0.1"
bindPort = 8500
The secretKey in the example must be replaced with another random secret and must not be shared with the frps Token. The visitor then opens http://127.0.0.1:8500. If only a few of your own devices need access, WireGuard or Tailscale is often simpler than maintaining an STCP visitor.
The FRP Dashboard shows clients, proxy names, connections and traffic information. The official configuration listens on 127.0.0.1 by default, which is a reasonable security boundary. If you really need the Dashboard, you can configure local listening and a separate random password in frps.toml, then access it through an SSH tunnel; do not directly allow 7500/TCP.
SSH tunnel access method:
ssh -L 7500:127.0.0.1:7500 [email protected]
Then open http://127.0.0.1:7500 in the browser. Prometheus metrics share the same address as the Dashboard, so enabling monitoring is also not a reason to expose the management port to the whole internet.
FRP is a single binary, so updates are not complicated, but you must consider configuration compatibility and brief disconnections. The security process is as follows:
- Check the official Release Notes and compatibility policy;
- Back up the current binary, TOML and Token;
- Download the new version for the corresponding architecture and verify the official SHA-256;
- Run
verifywith the new binary; - Update frps first, then update frpc on each machine;
- Check the control connection, all proxies and actual services;
- If it fails, restore the old binary and restart.
VPS-side backup:
backup_stamp=$(date +%Y%m%d-%H%M%S)
sudo install -d -m 700 "/root/frp-backup-${backup_stamp}"
sudo cp -a /usr/local/bin/frps /etc/frp \
"/root/frp-backup-${backup_stamp}/"
Verify before installing the new binary:
/tmp/new-frps verify -c /etc/frp/frps.toml
sudo cp /usr/local/bin/frps /usr/local/bin/frps.previous
sudo install -m 755 /tmp/new-frps /usr/local/bin/frps
sudo systemctl restart frps
sudo systemctl status frps --no-pager
sudo journalctl -u frps -n 100 --no-pager
If startup fails:
sudo install -m 755 /usr/local/bin/frps.previous /usr/local/bin/frps
sudo systemctl restart frps
Since v0.69.0, the official project has provided a compatibility support window across multiple minor versions, but features such as the new wire protocol may still require both ends to be upgraded at the same time. For long-term maintenance, keeping frps and frpc on similar versions makes troubleshooting simpler.
On the intranet device, test DNS, routing and the port:
ping -c 4 203.0.113.10
nc -vz 203.0.113.10 7000
sudo journalctl -u frpc -n 100 --no-pager
On the VPS, check:
sudo systemctl status frps --no-pager
sudo ss -lntp | grep ':7000'
sudo ufw status numbered
sudo journalctl -u frps -n 100 --no-pager
If local listening is normal but external connections time out, it is usually the cloud security group, UFW or upstream network blocking. connection refused is more likely to mean the process is not listening or the bind address is wrong.
Calculate the Token file hash on both ends separately, and compare the results without directly displaying the secret:
sudo sha256sum /etc/frp/server_token
sudo sha256sum /etc/frp/client_token
Run the first command on the server and the second on the client. The hashes must match. After changing the Token, update all client files first, then restart frps and frpc in a maintenance window to avoid some clients being permanently disconnected.
Check in three layers:
- Whether the intranet device itself can access
localIP:localPort; - Whether the VPS is listening on
remotePort; - Whether UFW and the cloud security group allow the correct protocol and source.
curl -I http://127.0.0.1:5000
sudo ss -lntup | grep -E ':6000|:8080'
sudo journalctl -u frpc -n 100 --no-pager
sudo journalctl -u frps -n 100 --no-pager
Also confirm that remotePort is within the allowPorts range of frps. When the port is not in the allowlist, frpc registration fails rather than automatically bypassing the restriction.
Test the "user to VPS" and "VPS to home broadband" routes separately; do not only test VPS download speed. Focus on:
- Whether home broadband upload is the bottleneck;
- Whether the VPS has packet loss at peak hours or CPU overselling;
- Whether
transport.bandwidthLimitis set too low; - Whether monthly traffic has already triggered provider throttling;
- Whether TCP-over-TCP and long-distance relaying amplify the impact of packet loss.
FRP supports KCP and QUIC, but switching transport protocols is not a universal optimization. They require additionally opening UDP and may increase CPU, policy and troubleshooting costs. First confirm the bottleneck with a stable TCP baseline, then decide based on actual measurements.
Public ports being scanned is normal and does not mean an attacker has logged in. You should confirm:
- The Dashboard only listens on
127.0.0.1; - The SSH mapping only allows administrator source IPs;
- The NAS and web applications themselves have strong authentication and multi-factor authentication enabled;
allowPortsdoes not expose a large range of useless ports;- The Token does not appear in Git, shell history, screenshots or logs;
- UFW, the cloud security group and application logs all have records.
FRP only forwards traffic and does not automatically fix vulnerabilities in the forwarded service itself. Exposing a NAS admin panel without a password to the public internet through FRP carries essentially the same risk as direct port mapping.
- The VPS has a dedicated public IP, and the provider allows reverse proxying and the planned traffic scale;
- Both
frpsandfrpcare downloaded from official Releases and pass SHA-256 verification; - TOML is used, and deprecated INI examples are no longer copied;
- frps runs as a dedicated
frpuser, not root; - The Token is in a file with controlled permissions and is not written in plaintext into Git;
transport.tls.force = true, and you understand that default TLS does not verify server identity;allowPortsandmaxPortsPerClienthave restricted proxy permissions;7000/TCP, business ports, UFW and the cloud security group are consistent;- The Dashboard is not exposed to the public internet;
- SSH, NAS and web applications still retain their own authentication and update mechanisms;
- Automatic recovery of frps/frpc after reboot has been tested;
- Traffic monitoring and overage alerts are configured, and a configuration backup and rollback drill has been completed.
There must be a server that both ends can access, usually with a public IP, to run frps. It can be a cloud server, VPS or other public host; for most individual users, a low-spec VPS is the easiest choice for long-term maintenance.
A small amount of SSH and management traffic is usually fine, but you also need to leave room for system updates, logs and monitoring. A large number of connections and high-speed relaying depend more on bandwidth, traffic and network stability, not just memory.
Ordinary TCP, UDP, HTTP, HTTPS and STCP proxies usually pass through the VPS, so they may count toward VPS traffic. XTCP attempts peer-to-peer transmission, but it is affected by NAT type and success is not guaranteed.
They cannot simply replace each other. FRP is good at mapping specific services outward; WireGuard is good at letting authorized devices enter a private network. If only your own devices access the home network, WireGuard is usually safer and requires less configuration.
No. The Token authenticates frpc and does not perform user authentication for the forwarded SSH, NAS or web services. You still need to restrict port sources, enable TLS, use strong passwords or keys, and update applications promptly.
The Dashboard contains client, proxy and traffic information, and it also expands the management attack surface. Listening on 127.0.0.1 and viewing it through an SSH tunnel lets you avoid opening one more public management entry point.
A successful FRP deployment is not measured by "frpc shows online", but by actually verifying the public entry point, intranet services, authentication, HTTPS, firewall, traffic alerts and reboot recovery. First run the complete chain with a low-risk SSH or test web service, then gradually migrate the NAS, photo albums and other long-term services.