kubo/docs/p2p-tunnels.md
Marcin Rataj 25ebab9dae
feat(p2p): add --foreground flag to listen and forward commands (#11099)
* feat(p2p): add --foreground flag to listen and forward commands

adds `-f/--foreground` option that keeps the command running until
interrupted (SIGTERM/Ctrl+C) or closed via `ipfs p2p close`. the
listener/forwarder is automatically removed when the command exits.

useful for systemd services and scripts that need cleanup on exit.

* docs: add p2p-tunnels.md with systemd examples

- add dedicated docs/p2p-tunnels.md covering:
  - why p2p tunnels (NAT traversal, no public IP needed)
  - quick start with netcat
  - background and foreground modes
  - systemd integration with path-based activation
  - security considerations and troubleshooting
- document Experimental.Libp2pStreamMounting in docs/config.md
- simplify docs/experimental-features.md, link to new doc
- add "Learn more" links to ipfs p2p listen/forward --help
- update changelog entry with doc link
- add cross-reference in misc/README.md

* chore: reference kubo#5460 for p2p config

Ref. https://github.com/ipfs/kubo/issues/5460

* fix(daemon): write api/gateway files only after HTTP server is ready

fixes race condition where $IPFS_PATH/api and $IPFS_PATH/gateway files
were written before the HTTP servers were ready to accept connections.
this caused issues for tools like systemd path units that immediately
try to connect when these files appear.

changes:
- add corehttp.ServeWithReady() that signals when server is ready
- wait for ready signal before writing address files
- use sync.WaitGroup.Go() (Go 1.25) for cleaner goroutine management
- add TestAddressFileReady to verify both api and gateway files

* fix(daemon): buffer errc channel and wait for all listeners

- buffer error channel with len(listeners) to prevent deadlock when
  multiple servers write errors simultaneously
- wait for ALL listeners to be ready before writing api/gateway file,
  not just the first one

Feedback-from: https://github.com/ipfs/kubo/pull/11099#pullrequestreview-3593885839

* docs(changelog): improve p2p tunnel section clarity

reframe to lead with user benefit and add example output

* docs(p2p): remove obsolete race condition caveat

the "First launch fails but restarts work" troubleshooting section
described a race where the api file was written before the daemon was
ready. this was fixed in 80b703a which ensures api/gateway files are
only written after HTTP servers are ready to accept connections.

---------

Co-authored-by: Andrew Gillis <11790789+gammazero@users.noreply.github.com>
2026-01-09 19:22:43 +01:00

6.4 KiB

P2P Tunnels

Kubo supports tunneling TCP connections through libp2p streams, similar to SSH port forwarding (ssh -L). This allows exposing local services to remote peers and forwarding remote services to local ports.

Why P2P Tunnels?

Unlike traditional SSH tunnels, libp2p-based tunnels do not require:

  • No public IP or open ports: The server does not need a static IP address or port forwarding configured on the router. Connectivity to peers behind NAT is facilitated by Direct Connection Upgrade through Relay (DCUtR), which enables NAT hole-punching.

  • No DNS or IP address management: All you need is the server's PeerID and an agreed-upon protocol name (e.g., /x/ssh). Kubo handles peer discovery and routing via the Amino DHT.

  • Simplified firewall rules: Since connections are established through libp2p's existing swarm connections, no additional firewall configuration is needed beyond what Kubo already requires.

This makes p2p tunnels useful for connecting to machines on home networks, behind corporate firewalls, or in environments where traditional port forwarding is not available.

Quick Start

Enable the experimental feature:

$ ipfs config --json Experimental.Libp2pStreamMounting true

Test with netcat (nc) - no services required:

On the server:

$ ipfs p2p listen /x/test /ip4/127.0.0.1/tcp/9999
$ nc -l -p 9999

On the client:

Replace $SERVER_ID with the server's peer ID (get it with ipfs id -f "<id>\n" on the server).

$ ipfs p2p forward /x/test /ip4/127.0.0.1/tcp/9998 /p2p/$SERVER_ID
$ nc 127.0.0.1 9998

Type in either terminal and the text appears in the other. Use Ctrl+C to exit.

Background Mode

By default, ipfs p2p listen and ipfs p2p forward register the tunnel with the daemon and return immediately. The tunnel persists until explicitly closed with ipfs p2p close or the daemon shuts down.

This example exposes a local SSH server (listening on localhost:22) to a remote peer. The same pattern works for any TCP service.

On the server (the machine running SSH):

Register a p2p listener that forwards incoming connections to the local SSH server. The protocol name /x/ssh is an arbitrary identifier that both peers must agree on (the /x/ prefix is required for custom protocols).

$ ipfs p2p listen /x/ssh /ip4/127.0.0.1/tcp/22

On the client:

Create a local port (2222) that tunnels through libp2p to the server's SSH service.

$ ipfs p2p forward /x/ssh /ip4/127.0.0.1/tcp/2222 /p2p/$SERVER_ID

Now connect to SSH through the tunnel:

$ ssh user@127.0.0.1 -p 2222

Other services: To tunnel a different service, change the port and protocol name. For example, to expose a web server on port 8080, use /x/mywebapp and /ip4/127.0.0.1/tcp/8080.

Foreground Mode

Use --foreground (-f) to block until interrupted. The tunnel is automatically removed when the command exits:

$ ipfs p2p listen /x/ssh /ip4/127.0.0.1/tcp/22 --foreground
Listening on /x/ssh, forwarding to /ip4/127.0.0.1/tcp/22, waiting for interrupt...
^C
Received interrupt, removing listener for /x/ssh

The listener/forwarder is automatically removed when:

  • The command receives Ctrl+C or SIGTERM
  • ipfs p2p close is called
  • The daemon shuts down

This mode is useful for systemd services and scripts that need cleanup on exit.

systemd Integration

The --foreground flag enables clean integration with systemd. The examples below show how to run ipfs p2p listen as a user service that starts automatically when the IPFS daemon is ready.

Ensure IPFS daemon runs as a systemd user service. See misc/README.md for setup instructions and where to place unit files.

P2P listener with path-based activation

Use a .path unit to wait for the daemon's RPC API to be ready before starting the p2p listener.

ipfs-p2p-tunnel.path:

[Unit]
Description=Monitor for IPFS daemon startup
After=ipfs.service
Requires=ipfs.service

[Path]
PathExists=%h/.ipfs/api
Unit=ipfs-p2p-tunnel.service

[Install]
WantedBy=default.target

The %h specifier expands to the user's home directory. If you use a custom IPFS_PATH, adjust accordingly.

ipfs-p2p-tunnel.service:

[Unit]
Description=IPFS p2p tunnel
Requires=ipfs.service

[Service]
ExecStart=ipfs p2p listen /x/ssh /ip4/127.0.0.1/tcp/22 -f
Restart=on-failure
RestartSec=10

[Install]
WantedBy=default.target

Enabling the services

$ systemctl --user enable ipfs.service
$ systemctl --user enable ipfs-p2p-tunnel.path
$ systemctl --user start ipfs.service

The path unit monitors ~/.ipfs/api and starts ipfs-p2p-tunnel.service once the file exists.

Security Considerations

Warning

This feature provides CLI and HTTP RPC users with the ability to set up port forwarding for localhost and LAN ports. If you enable this and plan to expose CLI or HTTP RPC to other users or machines, secure the RPC API using API.Authorizations or custom auth middleware.

Troubleshooting

Foreground listener stops when terminal closes

When using --foreground, the listener stops if the terminal closes. For persistent foreground listeners, use a systemd service, nohup, tmux, or screen. Without --foreground, the listener persists in the daemon regardless of terminal state.

Connection refused errors

Verify:

  1. The experimental feature is enabled: ipfs config Experimental.Libp2pStreamMounting
  2. The listener is active: ipfs p2p ls
  3. Both peers can connect: ipfs swarm connect /p2p/$PEER_ID

Persistent tunnel configuration

There is currently no way to define tunnels in the Kubo JSON config file. Use --foreground mode with a systemd service for persistent tunnels. Support for configuring tunnels via JSON config may be added in the future (see kubo#5460 - PRs welcome!).