A small FRP client package can be more useful than a full installer when the goal is temporary remote access, rapid deployment, or distribution to a limited number of trusted users.
The key is not simply to copy frpc.exe together with a configuration file. A good portable package should be:
- self-contained;
- easy to start and stop;
- able to report its own state;
- safe when multiple FRPC instances exist on the same system;
- isolated from other recipients;
- usable on both Windows and Linux;
- easy to verify before distribution.
A clean design can achieve all of this without installing FRPC as a system service on the client machine.
1. The Core Design Goal
The intended architecture is based on one independent FRPS environment per distributed FRPC package.
Instead of sharing one server-side FRPS process among multiple recipients:
Shared FRPS
/ | \
User A User B User C
each package is assigned its own FRPS instance:
FRPC-A ───────► FRPS-A
FRPC-B ───────► FRPS-B
FRPC-C ───────► FRPS-C
Each pair has its own:
FRPS process
FRPS configuration
control port
authentication token
remote-port whitelist
FRPC configuration
For example:
FRPC-A
↓ QUIC/TLS
<FRPS_DOMAIN_A>:<FRPS_PORT_A>
↓
FRPS-A
↓
<REMOTE_SSH_PORT_A>
↓
FRPC-A host:22
and independently:
FRPC-B
↓ QUIC/TLS
<FRPS_DOMAIN_B>:<FRPS_PORT_B>
↓
FRPS-B
↓
<REMOTE_SSH_PORT_B>
↓
FRPC-B host:22
A compromised configuration from one package therefore does not automatically grant access to another FRPS security domain.
2. Server-Side Multi-Instance Layout
A simple FRPS directory can keep one executable while using multiple configuration files:
<FRPS_DIRECTORY>/
├── frps
├── frps1.toml
├── frps2.toml
└── frps3.toml
Each FRPS instance is then managed separately by systemd:
frps1.service → frps1.toml
frps2.service → frps2.toml
frps3.service → frps3.toml
A generic service definition looks like:
[Unit]
Description=frps<N>-server
[Service]
User=<SERVICE_USER>
WorkingDirectory=<FRPS_DIRECTORY>
ExecStart=<FRPS_DIRECTORY>/frps -c <FRPS_DIRECTORY>/frps<N>.toml
Restart=always
[Install]
WantedBy=multi-user.target
The benefit is operational isolation.
Restarting:
systemctl restart frps2.service
does not affect:
frps1.service
frps3.service
3. Minimal FRPS Configuration for Portable Clients
A portable-client FRPS instance does not necessarily need a web dashboard.
A minimal configuration can look like:
#################################### Communication ###############################
bindPort = <FRPS_TCP_PORT>
quicBindPort = <FRPS_UDP_PORT>
userConnTimeout = 30
#################################### Authentication ##############################
auth.method = "token"
auth.token = "<UNIQUE_AUTH_TOKEN>"
auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
#################################### Allowed Remote Ports ########################
allowPorts = [
{ single = <REMOTE_SSH_PORT> }
]
#################################### Transport ###################################
transport.tls.force = true
If TCP and QUIC intentionally use the same numerical port:
bindPort = <FRPS_PORT>
quicBindPort = <FRPS_PORT>
the firewall must allow both:
<FRPS_PORT>/tcp
<FRPS_PORT>/udp
The SSH proxy itself only requires:
<REMOTE_SSH_PORT>/tcp
There is no reason to open the SSH remote port over UDP.
4. Why allowPorts Matters
A client possessing a valid FRPS token can request remote proxy ports.
Without a restriction, the client may potentially request arbitrary available ports on the FRPS machine.
Therefore each isolated FRPS instance should ideally contain a strict whitelist:
allowPorts = [
{ single = <REMOTE_SSH_PORT> }
]
A temporary SSH-only FRPS instance therefore has access to exactly one server-side published port.
This creates a useful security boundary:
Authentication token
+
Remote-port whitelist
+
Independent FRPS process
5. Client Configuration
The client configuration can remain very small.
For an SSH-only package:
#################################### Server ######################################
serverAddr = "<FRPS_DOMAIN>"
serverPort = <FRPS_PORT>
#################################### Transport ###################################
transport.protocol = "quic"
transport.tls.enable = true
#################################### Authentication ##############################
auth.method = "token"
auth.token = "<UNIQUE_AUTH_TOKEN>"
auth.additionalScopes = ["HeartBeats", "NewWorkConns"]
#################################### Proxy ########################################
[[proxies]]
name = "<PACKAGE_NAME> SSH"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = <REMOTE_SSH_PORT>
Using:
localIP = "127.0.0.1"
instead of:
localIP = "localhost"
removes hostname-resolution ambiguity and makes the local target explicit.
6. Portable Package Structure
A cross-platform package can include both Linux and Windows binaries:
frpc<N>/
├── frpc
├── frpc.exe
├── frpc<N>.toml
├── FRPC-Start.bat
├── FRPC-Status.bat
├── FRPC-Stop.bat
├── FRPC-Start.sh
├── FRPC-Status.sh
└── FRPC-Stop.sh
The lowercase package directory is convenient for Linux and other case-sensitive systems:
frpc2/
frpc3/
while the control scripts retain the explicit FRPC prefix:
FRPC-Start
FRPC-Status
FRPC-Stop
This is useful if one of the scripts is copied outside its intended directory. A generic filename such as:
Start.bat
reveals almost nothing about what it controls.
By contrast:
FRPC-Start.bat
is immediately recognizable.
7. Why Start, Status, and Stop Scripts Are Worth Adding
A raw FRPC package normally requires the user to open a terminal and run something like:
frpc -c frpc<N>.toml
That is acceptable for administrators, but inconvenient for casual recipients.
A portable distribution benefits from three simple operations:
FRPC-Start
FRPC-Status
FRPC-Stop
The package then behaves almost like a small application without requiring installation.
8. Windows Start Behavior
The Windows start script should:
- locate its own directory;
- locate the matching TOML file;
- detect whether the same FRPC instance is already running;
- start
frpc.exein the background; - avoid leaving a console window open;
- record runtime information;
- display a success or failure popup.
A hidden runtime directory can store temporary state:
.runtime\
├── frpc.pid
├── frpc.log
└── frpc-error.log
The user-facing directory remains clean.
9. Do Not Kill FRPC by Process Name
A dangerous stop implementation would be:
taskkill /IM frpc.exe /F
This kills every FRPC process on the machine.
That becomes a serious problem if a Windows host happens to run several independent FRPC instances.
A safer design identifies the intended process using a combination of:
executable path
configuration filename
PID
command line
For example, a process should only be considered part of package frpc2 if it matches something equivalent to:
<PACKAGE_DIRECTORY>\frpc.exe
-c <PACKAGE_DIRECTORY>\frpc2.toml
The stop script can then terminate only that exact process.
10. PID Files Should Not Be Trusted Blindly
A PID file is useful:
.runtime/frpc.pid
but operating systems eventually reuse process IDs.
Therefore a robust status or stop script should not simply read:
12345
and kill PID 12345.
It should verify that the PID still belongs to:
the expected FRPC executable
+
the expected configuration
If not, the PID file is stale and should be removed.
11. Status Script Design
The status script should answer one simple question:
Is this package's FRPC process running?
On Windows, a popup is convenient:
FRPC is running.
PID: <PID>
or:
FRPC is not running.
A popup avoids the awkward behavior of a command window appearing briefly and immediately disappearing.
For terminal-only environments, a status script can instead print the result and optionally wait for:
Press any key to exit...
12. Linux Script Behavior
Linux packages can use the same conceptual Start / Status / Stop interface.
A startup script can launch FRPC with:
nohup ./frpc -c ./frpc<N>.toml > .runtime/frpc.log 2>&1 </dev/null &
and record:
$!
as the PID.
On desktop Linux, scripts can display graphical dialogs when available:
KDE → kdialog
GNOME/etc → zenity
and fall back to terminal output otherwise.
This makes the same package usable both from:
desktop file manager
and:
SSH / terminal
13. Linux Process Validation
Linux provides an especially reliable way to verify process identity through /proc.
For a stored PID:
/proc/<PID>/exe
/proc/<PID>/cmdline
can be inspected.
A valid package FRPC process should satisfy both conditions:
/proc/<PID>/exe
=
<PACKAGE_DIRECTORY>/frpc
and its command line should contain:
frpc<N>.toml
This prevents a stop script from terminating another FRPC process merely because the process name happens to be the same.
14. Cross-Platform Binary Packaging
If a package targets x86-64 systems, it can contain:
frpc → Linux x86-64 ELF
frpc.exe → Windows x86-64 PE
The configuration file is shared between operating systems.
For ARM64 Linux machines, the Linux executable must instead be replaced with the ARM64 build:
frpc → Linux ARM64
The scripts and TOML configuration generally do not need to change.
15. Matching Client and Server Versions
For controlled deployments, using the same FRP release on client and server simplifies compatibility testing.
For example:
FRPS: <FRP_VERSION>
FRPC Linux: <FRP_VERSION>
FRPC Windows: <FRP_VERSION>
A portable package should ideally be assembled from one explicitly selected FRP release rather than mixing binaries downloaded at different times.
16. Distribution Archive
Each recipient should receive a separate archive:
frpc2.zip
frpc3.zip
rather than one archive containing both configurations.
The separation is important because the TOML contains credentials.
Conceptually:
Recipient A
├── frpc2.zip
└── frpc2.zip.sha256
Recipient B
├── frpc3.zip
└── frpc3.zip.sha256
No recipient needs to know another package exists.
17. SHA-256 Verification
A professional-looking portable package should include an external checksum file.
Generate it with:
sha256sum frpc<N>.zip > frpc<N>.zip.sha256
The result contains:
<SHA256_HASH> frpc<N>.zip
The recipient can verify it with:
sha256sum -c frpc<N>.zip.sha256
Expected result:
frpc<N>.zip: OK
The .sha256 file should remain outside the ZIP it verifies.
Otherwise it cannot independently validate the archive.
18. Why Separate Checksum Files Are Better Here
A project distributing many public release artifacts often uses:
SHA256SUMS
containing all package hashes.
For private recipient-specific packages, individual files are cleaner:
frpc2.zip.sha256
frpc3.zip.sha256
Each recipient only receives information about their own package.
19. Test the Actual Archive, Not Just the Development Directory
One of the easiest packaging mistakes is validating only the source directory.
A more reliable release procedure is:
build package
↓
create ZIP
↓
generate SHA-256
↓
copy ZIP elsewhere
↓
verify SHA-256
↓
extract into a new directory
↓
test extracted package
This simulates what the recipient will actually experience.
It catches problems such as:
missing files
wrong directory names
lost executable permissions
stale configuration
incorrect archive contents
broken scripts
20. Recommended Linux Validation Procedure
A clean Linux validation sequence is:
sha256sum -c frpc<N>.zip.sha256
then:
unzip frpc<N>.zip
verify the binary:
./frpc --version
verify configuration syntax:
./frpc verify -c ./frpc<N>.toml
start through the distributed script:
./FRPC-Start.sh
check status:
./FRPC-Status.sh
and verify the remote SSH path:
ssh-keyscan -p <REMOTE_SSH_PORT> <PUBLIC_DOMAIN>
Finally:
./FRPC-Stop.sh
and confirm no FRPC process remains.
21. End-to-End Validation Is More Important Than Config Validation
A valid TOML file only proves syntax.
A complete test should verify the entire path:
Local SSH :22
↑
Portable FRPC
↑
QUIC + TLS
↑
Independent FRPS
↑
Server firewall
↑
Router/NAT
↑
Public remote SSH port
A command such as:
ssh-keyscan -p <REMOTE_SSH_PORT> <PUBLIC_DOMAIN>
is useful because it confirms that an actual SSH server is reachable through the complete chain without requiring an interactive login.
A successful result resembles:
# <PUBLIC_DOMAIN>:<REMOTE_SSH_PORT> SSH-2.0-OpenSSH_<VERSION>
followed by host keys.
At that point the forwarding path is operational.
22. Firewall Design
The FRPS host needs two different categories of firewall rules.
For the FRPC-to-FRPS transport:
<FRPS_PORT>/tcp
<FRPS_PORT>/udp
For a TCP SSH proxy:
<REMOTE_SSH_PORT>/tcp
Therefore:
FRPS control transport:
TCP + UDP
Published SSH proxy:
TCP only
The same distinction should also be reflected in the edge router’s NAT rules.
23. A Useful Security Property of This Design
Each package has an independent revocation path.
If package A should no longer work, the administrator can remove or disable:
FRPS-A service
FRPS-A firewall rule
router forwarding rule
FRPS-A token
without touching package B.
This is much cleaner than several recipients sharing:
one FRPS instance
one token
one control port
one policy set
24. Final Architecture
The resulting architecture resembles:
Internet
│
<EDGE_ROUTER>
│
┌────────────────┴────────────────┐
│ │
<FRPS_PORT_A> <FRPS_PORT_B>
TCP + UDP TCP + UDP
│ │
▼ ▼
FRPS-A FRPS-B
│ │
<REMOTE_SSH_A>/TCP <REMOTE_SSH_B>/TCP
│ │
▼ ▼
FRPC-A FRPC-B
│ │
localhost:22 localhost:22
The two paths share physical infrastructure but not FRPS processes, credentials, control ports, or published SSH ports.
25. Final Package Layout
A polished distribution can ultimately look like:
frpc<N>/
├── frpc
├── frpc.exe
├── frpc<N>.toml
│
├── FRPC-Start.bat
├── FRPC-Status.bat
├── FRPC-Stop.bat
│
├── FRPC-Start.sh
├── FRPC-Status.sh
└── FRPC-Stop.sh
distributed as:
frpc<N>.zip
frpc<N>.zip.sha256
Internally, runtime files are created only after execution:
.runtime/
├── frpc.pid
├── frpc.log
└── frpc-error.log
Conclusion
A portable FRPC package can be much more than an executable and a TOML file.
With a small amount of structure, it becomes a controlled, cross-platform remote-access component with:
independent authentication
independent FRPS processes
restricted remote ports
QUIC/TLS transport
Windows and Linux binaries
Start / Status / Stop controls
precise process identification
runtime logging
SHA-256 verification
recipient-level isolation
The most important design principle is isolation.
Each recipient should be treated as a separate security boundary rather than merely another configuration pointing at the same FRPS instance.
Once that principle is combined with strict port whitelisting, precise process management, reproducible packaging, and end-to-end testing of the actual archive, FRPC becomes suitable for temporary, portable deployment without sacrificing operational clarity.