Self-Host a Bore Tunnel Server on a RamNode VPS
Lightweight, open-source TCP tunneling written in Rust. Expose local ports to the internet through a server you control — no usage limits, no accounts, no third-party involvement.
What Is Bore?
Bore has two components: bore server runs on your VPS and listens for incoming tunnel connections, and bore local runs on your local machine and forwards a local port through the server. When a client connects, bore assigns a remote port on the server side. Anyone connecting to your-vps:remote-port gets routed transparently to localhost:local-port on the client machine.
Requirements
- A RamNode VPS running Ubuntu 22.04 LTS or Debian 12
- Root or sudo access
- A domain name pointed at your VPS (optional but recommended)
- Port 7835 open for control connections (configurable)
- Port range open for tunneled connections (recommended: 10000–20000)
Update the System
apt update && apt upgrade -yInstall Bore
Bore ships as a single static binary. Download the latest release from GitHub:
BORE_VERSION=$(curl -s https://api.github.com/repos/ekzhang/bore/releases/latest \
| grep '"tag_name"' | cut -d '"' -f 4)
curl -Lo /tmp/bore.tar.gz \
"https://github.com/ekzhang/bore/releases/download/${BORE_VERSION}/bore-${BORE_VERSION}-x86_64-unknown-linux-musl.tar.gz"
tar -xzf /tmp/bore.tar.gz -C /tmp
install -m 755 /tmp/bore /usr/local/bin/borebore --versionCreate a Dedicated Service User
Running bore under a dedicated unprivileged user limits blast radius if anything goes wrong:
useradd --system --no-create-home --shell /usr/sbin/nologin boreConfigure the Firewall
Allow the bore control port and a restricted range for tunneled connections:
ufw allow 7835/tcp comment "bore control"
ufw allow 10000:20000/tcp comment "bore tunnels"
ufw enableAdjust the port range to match your bore server configuration.
Create the systemd Service
For production, always set a --secret. Without one, anyone who knows your server address can create tunnels through it.
openssl rand -hex 32[Unit]
Description=Bore Tunnel Server
After=network.target
[Service]
Type=simple
User=bore
Group=bore
ExecStart=/usr/local/bin/bore server \
--control-port 7835 \
--min-port 10000 \
--max-port 20000 \
--secret YOUR_SECRET_HERE
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
[Install]
WantedBy=multi-user.targetReplace YOUR_SECRET_HERE with the generated value.
| Flag | Default | Description |
|---|---|---|
--control-port | 7835 | Port bore clients connect to |
--min-port | 1024 | Minimum port for tunnel assignments |
--max-port | 65535 | Maximum port for tunnel assignments |
--secret | (none) | Shared secret to restrict access |
Start and Enable the Service
systemctl daemon-reload
systemctl enable bore
systemctl start bore
systemctl status bore● bore.service - Bore Tunnel Server
Loaded: loaded (/etc/systemd/system/bore.service; enabled)
Active: active (running) since ...Test the Connection from a Local Machine
Install bore on your local machine, then open a tunnel:
brew install bore-clibore local 3000 --to your-vps-ip --secret YOUR_SECRET_HERE2024-01-15T12:00:00Z INFO bore_cli::client: listening at your-vps-ip:10432Your local port 3000 is now reachable at http://your-vps-ip:10432 from anywhere.
Pin a Specific Remote Port (Optional)
Use the --port flag for a consistent port — useful for webhooks or shared dev environments:
bore local 3000 --to your-vps-ip --port 10080 --secret YOUR_SECRET_HERESecurity Hardening
Restrict tunnel port access by IP
ufw allow from 203.0.113.42 to any port 10000:20000 proto tcp
ufw delete allow 10000:20000/tcpRotate the secret
Edit /etc/systemd/system/bore.service, update the secret value, then:
systemctl daemon-reload
systemctl restart borePutting a Domain in Front (Optional)
Use nginx as a reverse proxy to route a subdomain to a specific tunnel port (e.g., dev.yourdomain.com instead of your-vps-ip:10080).
apt install nginx -yserver {
listen 80;
server_name dev.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:10080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}ln -s /etc/nginx/sites-available/bore-tunnel /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
apt install certbot python3-certbot-nginx -y
certbot --nginx -d dev.yourdomain.comUpdating Bore
systemctl stop bore
BORE_VERSION=$(curl -s https://api.github.com/repos/ekzhang/bore/releases/latest \
| grep '"tag_name"' | cut -d '"' -f 4)
curl -Lo /tmp/bore.tar.gz \
"https://github.com/ekzhang/bore/releases/download/${BORE_VERSION}/bore-${BORE_VERSION}-x86_64-unknown-linux-musl.tar.gz"
tar -xzf /tmp/bore.tar.gz -C /tmp
install -m 755 /tmp/bore /usr/local/bin/bore
systemctl start bore
bore --versionQuick Reference
| Task | Command |
|---|---|
| Start server | systemctl start bore |
| Stop server | systemctl stop bore |
| View logs | journalctl -u bore -f |
| Open tunnel | bore local PORT --to VPS_IP --secret SECRET |
| Fixed port tunnel | bore local PORT --to VPS_IP --port REMOTE --secret SECRET |
Troubleshooting
Client cannot connect to the server
Check that port 7835 is open in UFW and that the bore service is running. Verify with systemctl status bore and journalctl -u bore -f.
Tunnel connects but service is unreachable
Ensure the tunnel port range (10000–20000) is open in UFW. Check that the assigned port falls within your configured range.
Authentication failed
The secret on the client must exactly match the server. Check for trailing whitespace or mismatched values.
