Intranet Penetration with frp

📢 This article was translated by gemini-3.5-flash

Set up intranet penetration using frp with a full Docker deployment. Tested the speed, and it’s pretty good with acceptable latency.

DNS Configuration

You’ll need a server with a public IP. I handle my SSL certificates via Cloudflare, so all my connections go through port 80.

Configure a few domains pointing to your server: one for connecting to frp (e.g., frp.example.com), and others for the services you want to proxy (e.g., server1.example.com, server2.example.com). Note that the frp connection domain (frp.example.com) must not use Cloudflare proxying—keep it on “DNS only” mode.

Server Side

Setting up frp on the server is pretty straightforward. Prepare the config file frps.toml:

1
2
3
4
5
bindPort = 7000
vhostHTTPPort = 8000

auth.method = "token"
auth.token = "set_your_token"

Set a secure token, then create the docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
services:
  frps:
    image: snowdreamtech/frps:latest
    container_name: frps
    restart: unless-stopped
    ports:
      - "7000:7000"
      - "8000:8000"
    volumes:
      - ./frps.toml:/etc/frp/frps.toml

Spin it up:

1
docker compose up -d

Nginx Configuration

Reverse proxy the frp connection domain (frp.example.com) to bindPort = 7000 (which is 172.17.0.1:7000).

Then, reverse proxy all service domains to vhostHTTPPort = 8000 (which is 172.17.0.1:8000).

Since Cloudflare proxying is enabled, you can’t just bundle these domains under a single Nginx server_name directive, or only the first domain will work. You’ll need separate configurations for each domain, with each one proxying back to 172.17.0.1:8000.

Client Side

The client-side configuration is slightly more complex. Here is the config file frpc.toml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
serverAddr = "frp.example.com"
serverPort = 80
transport.protocol = "websocket"

auth.method = "token"
auth.token = "set_your_token"

[[proxies]]
name = "nas-server1"
type = "http"
localIP = "192.168.6.x"
localPort = 8001
customDomains = ["server1.example.com"]

[[proxies]]
name = "nas-server2"
type = "http"
localIP = "192.168.6.x"
localPort = 8002
customDomains = ["server2.example.com"]

And the docker-compose.yml:

1
2
3
4
5
6
7
8
services:
  frpc:
    image: snowdreamtech/frpc:latest
    container_name: frpc
    restart: unless-stopped
    network_mode: "host"
    volumes:
      - "./frpc.toml:/etc/frp/frpc.toml"

Start it up:

1
docker compose up -d