Expose a service, and only to the machines that should see it
Thirteen recipes for publishing a database or a self-hosted app on the overlay. Each keeps the service on loopback, where it already listens, and publishes it to every machine that shares the server's taint, and to nothing else: no port forward, no firewall rule, nothing on the internet.
Before you start
Every recipe uses the same two machines: a server running the service, and a client that wants to reach it. If conflux is not installed yet, install it on both.
- On the server, publish the port
The first run mints a taint and asks nothing else, because
--ipv4answers the one question it would.$ sudo conflux proxy 5432=127.0.0.1:5432 --ipv4 10.128.0.5/24 Minted a taint for this network: brhk-2mq9-tzva-6pjs-k4xe-nw7d-qf - On each client, join with that taint
$ sudo conflux up --taint brhk-2mq9-tzva-6pjs-k4xe-nw7d-qf
- Connect to the server's overlay address
The recipes use
10.128.0.5, the IPv4 from the first step. The server's IPv6 address works as well, bracketed in a URL.sudo conflux peerslists both for every peer, and peers take fifteen to sixty seconds to find each other.
- A client runs
conflux up. A machine runningconflux proxyhas no interface to connect out from. - Leave the service on loopback. In Docker, publish to
127.0.0.1: conflux dials from the host, where container names do not resolve. - The service sees every connection come from loopback. Keep its own authentication on: the overlay decides who can connect, not who may sign in.
- Server already running
conflux up? Then there is no proxy: bind the service to the overlay address instead. Each recipe says how.
Choose a service
PostgreSQL
Reach a database from laptops, CI runners and app servers without opening 5432 to anything else.
# PostgreSQL listens on localhost by default; leave it there $ sudo conflux proxy 5432=127.0.0.1:5432 --ipv4 10.128.0.5/24
$ docker run -d --name postgres -e POSTGRES_PASSWORD='change-me' \
-p 127.0.0.1:5432:5432 postgres:17
$ sudo conflux proxy 5432=127.0.0.1:5432 --ipv4 10.128.0.5/24$ psql "postgresql://app@10.128.0.5:5432/app" $ psql "postgresql://app@[fd80:c4b9:99ae:4411:c531:fd4f:754f:f08a]:5432/app"
If the server runs conflux up instead
# postgresql.conf listen_addresses = 'localhost,10.128.0.5' # pg_hba.conf: the client's own overlay IPv6, which is bound to its identity host app app fd80:c4b9:99ae:df9b:5261:d178:18f1:783c/128 scram-sha-256
Have clients connect to the server's IPv6 address, so that pg_hba.conf sees who each one is. Over IPv4 they arrive from the shared 198.18.0.0/15 range.
MySQL and MariaDB
Share one MySQL or MariaDB server with the machines that need it, and nothing else.
# keep bind-address = 127.0.0.1 in my.cnf $ sudo conflux proxy 3306=127.0.0.1:3306 --ipv4 10.128.0.5/24
$ docker run -d --name mariadb -e MARIADB_ROOT_PASSWORD='change-me' \
-p 127.0.0.1:3306:3306 mariadb:11
$ sudo conflux proxy 3306=127.0.0.1:3306 --ipv4 10.128.0.5/24$ mysql -h 10.128.0.5 -P 3306 -u app -p
If the server runs conflux up instead
# my.cnf [mysqld] bind-address = 10.128.0.5
Grant accounts to a client's overlay IPv6 address and have it connect over IPv6. A grant to an IPv4 address in 198.18.0.0/15 is a grant to whichever peer arrives from it.
Redis and Valkey
A shared cache or queue across machines and sites, with no port open to the internet.
# redis.conf: bind 127.0.0.1 -::1 (the default), and set a password requirepass change-me $ sudo conflux proxy 6379=127.0.0.1:6379 --ipv4 10.128.0.5/24
$ docker run -d --name valkey -p 127.0.0.1:6379:6379 \
valkey/valkey:8 valkey-server --requirepass 'change-me'
$ sudo conflux proxy 6379=127.0.0.1:6379 --ipv4 10.128.0.5/24$ redis-cli -h 10.128.0.5 -p 6379 --askpass $ valkey-cli -h 10.128.0.5 -p 6379 --askpass
If the server runs conflux up instead
# redis.conf bind 127.0.0.1 10.128.0.5 requirepass change-me
Bind the overlay address alongside loopback, and keep the password: the overlay decides who can connect, not who may run commands.
MongoDB
A document store your services reach across the overlay by address.
# mongod.conf: net.bindIp is 127.0.0.1 by default; leave it $ sudo conflux proxy 27017=127.0.0.1:27017 --ipv4 10.128.0.5/24
$ docker run -d --name mongo -p 127.0.0.1:27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD='change-me' \
mongo:8
$ sudo conflux proxy 27017=127.0.0.1:27017 --ipv4 10.128.0.5/24$ mongosh "mongodb://admin@10.128.0.5:27017/?authSource=admin"
If the server runs conflux up instead
# mongod.conf net: bindIp: 127.0.0.1,10.128.0.5 security: authorization: enabled
Bind the overlay address alongside loopback, with authorisation on.
Grafana
Dashboards for the whole team at a plain http://10.128.0.5/, with no reverse proxy to run.
# grafana.ini [server] http_addr = 127.0.0.1 root_url = http://10.128.0.5/ $ sudo conflux proxy 80=127.0.0.1:3000 --ipv4 10.128.0.5/24
$ docker run -d --name grafana -p 127.0.0.1:3000:3000 \
-e GF_SERVER_ROOT_URL=http://10.128.0.5/ grafana/grafana
$ sudo conflux proxy 80=127.0.0.1:3000 --ipv4 10.128.0.5/24# in a browser, on any machine that shares the taint http://10.128.0.5/
If the server runs conflux up instead
# grafana.ini [server] http_addr = 10.128.0.5 root_url = http://10.128.0.5:3000/
Grafana keeps its own port here, since a host port below 1024 needs privilege the overlay port does not.
Gitea and Forgejo
A private forge with clone over SSH and HTTP, reachable only from machines that share its taint.
# app.ini (Forgejo reads the same keys) [server] HTTP_ADDR = 127.0.0.1 ROOT_URL = http://10.128.0.5/ START_SSH_SERVER = true SSH_LISTEN_PORT = 2222 SSH_DOMAIN = 10.128.0.5 SSH_PORT = 22 $ sudo conflux proxy 80=127.0.0.1:3000 22=127.0.0.1:2222 --ipv4 10.128.0.5/24
$ docker run -d --name gitea -p 127.0.0.1:3000:3000 -p 127.0.0.1:2222:22 \
-e GITEA__server__ROOT_URL=http://10.128.0.5/ \
-e GITEA__server__SSH_DOMAIN=10.128.0.5 -e GITEA__server__SSH_PORT=22 \
gitea/gitea
$ sudo conflux proxy 80=127.0.0.1:3000 22=127.0.0.1:2222 --ipv4 10.128.0.5/24$ git clone git@10.128.0.5:team/app.git $ git clone http://10.128.0.5/team/app.git
If the server runs conflux up instead
# app.ini [server] HTTP_ADDR = 10.128.0.5 ROOT_URL = http://10.128.0.5:3000/
Over SSH, clients reach the host's own sshd at the overlay address, as on any network.
MinIO
S3-compatible storage for backups and artefacts, with its console beside it.
$ sudo conflux proxy 9000=127.0.0.1:9000 9001=127.0.0.1:9001 --ipv4 10.128.0.5/24
$ docker run -d --name minio -p 127.0.0.1:9000:9000 -p 127.0.0.1:9001:9001 \
-e MINIO_ROOT_USER=admin -e MINIO_ROOT_PASSWORD='change-me-please' \
-e MINIO_SERVER_URL=http://10.128.0.5:9000 \
minio/minio server /data --console-address :9001
$ sudo conflux proxy 9000=127.0.0.1:9000 9001=127.0.0.1:9001 --ipv4 10.128.0.5/24$ mc alias set overlay http://10.128.0.5:9000 admin 'change-me-please' $ mc ls overlay # the console, in a browser http://10.128.0.5:9001/
If the server runs conflux up instead
$ minio server /data --address 10.128.0.5:9000 --console-address 10.128.0.5:9001
Listen on the overlay address only, and set the server URL to the same address.
Prometheus
Query metrics from anywhere on the overlay, and scrape machines on other networks without opening exporters to them.
$ prometheus --web.listen-address=127.0.0.1:9090 $ sudo conflux proxy 9090=127.0.0.1:9090 --ipv4 10.128.0.5/24
$ docker run -d --name prometheus -p 127.0.0.1:9090:9090 prom/prometheus $ sudo conflux proxy 9090=127.0.0.1:9090 --ipv4 10.128.0.5/24
$ curl 'http://10.128.0.5:9090/api/v1/query?query=up'
If the server runs conflux up instead
# on each machine with an exporter, in the server's taint
$ sudo conflux proxy 9100=127.0.0.1:9100 --taint brhk-2mq9-tzva-6pjs-k4xe-nw7d-qf --ipv4 10.128.0.7/24
# prometheus.yml, on a Prometheus server running conflux up in the same taint
scrape_configs:
- job_name: node
static_configs:
- targets: ['10.128.0.7:9100', '10.128.0.8:9100']Scraping reaches outward, so the Prometheus server is the one that runs conflux up; each exporter can stay on loopback behind a proxy. Give every exporter and the server the same --taint: an exporter run without one mints its own, and the server sees DATA no for it.
Home Assistant
Your home from anywhere, with no port forward on the router and no cloud relay in the middle.
# Home Assistant listens on 8123 $ sudo conflux proxy 80=127.0.0.1:8123 --ipv4 10.128.0.5/24
$ docker run -d --name homeassistant --network=host \
-v /srv/homeassistant:/config ghcr.io/home-assistant/home-assistant:stable
$ sudo conflux proxy 80=127.0.0.1:8123 --ipv4 10.128.0.5/24# in a browser, on any machine that shares the taint http://10.128.0.5/
If the server runs conflux up instead
Home Assistant listens on every interface, so conflux up on its host is enough: it answers at http://10.128.0.5:8123/.
Nextcloud
Files, calendars and contacts for a team, reachable only by its own machines.
$ docker run -d --name nextcloud -p 127.0.0.1:8080:80 \
-v nextcloud:/var/www/html nextcloud
$ sudo conflux proxy 80=127.0.0.1:8080 --ipv4 10.128.0.5/24$ docker exec -u www-data nextcloud php occ config:system:set trusted_domains 1 --value=10.128.0.5 $ docker exec -u www-data nextcloud php occ config:system:set overwrite.cli.url --value=http://10.128.0.5
# in a browser, or as the server address in the desktop and mobile apps http://10.128.0.5/
If the server runs conflux up instead
Publish the container on all addresses, -p 8080:80, and add 10.128.0.5:8080 to trusted_domains the same way.
Jellyfin
Your media library on every device that shares the taint, at home or away.
$ sudo conflux proxy 8096=127.0.0.1:8096 --ipv4 10.128.0.5/24
$ docker run -d --name jellyfin -p 127.0.0.1:8096:8096 \
-v /srv/jellyfin/config:/config -v /srv/media:/media jellyfin/jellyfin
$ sudo conflux proxy 8096=127.0.0.1:8096 --ipv4 10.128.0.5/24# in a Jellyfin app: Add server http://10.128.0.5:8096
If the server runs conflux up instead
Jellyfin listens on every interface, so conflux up on its host is enough.
Pi-hole and AdGuard Home
Your own resolver and filter for every machine on the overlay, wherever it is. The UDP example.
$ sudo conflux proxy 53=127.0.0.1:53 53/udp=127.0.0.1:53 80=127.0.0.1:8080 --ipv4 10.128.0.5/24
$ docker run -d --name pihole \
-p 127.0.0.1:53:53/tcp -p 127.0.0.1:53:53/udp -p 127.0.0.1:8080:80 \
-e FTLCONF_webserver_api_password='change-me' pihole/pihole
$ sudo conflux proxy 53=127.0.0.1:53 53/udp=127.0.0.1:53 80=127.0.0.1:8080 --ipv4 10.128.0.5/24$ dig @10.128.0.5 example.com # the admin interface, in a browser http://10.128.0.5/admin/
If the server runs conflux up instead
Bind the resolver as usual, and check that its listening setting answers queries arriving on the overlay interface.
Ollama and Open WebUI
A private chat interface on your own GPU, shared with the team and with no model API open to anyone.
# Ollama stays on 127.0.0.1:11434, its default, and is never published $ open-webui serve --host 127.0.0.1 --port 8080 $ sudo conflux proxy 80=127.0.0.1:8080 --ipv4 10.128.0.5/24
$ docker network create ai
$ docker run -d --name ollama --network ai -v ollama:/root/.ollama ollama/ollama
$ docker run -d --name open-webui --network ai -p 127.0.0.1:3000:8080 \
-e OLLAMA_BASE_URL=http://ollama:11434 \
-v open-webui:/app/backend/data ghcr.io/open-webui/open-webui:main
$ sudo conflux proxy 80=127.0.0.1:3000 --ipv4 10.128.0.5/24# in a browser, on any machine that shares the taint http://10.128.0.5/
If the server runs conflux up instead
# to share Ollama's own API with your tools, on a host running conflux up $ OLLAMA_HOST=10.128.0.5 ollama serve
Only do this inside a compartment of trusted machines: the API has no authentication of its own.
Several services on one machine
Each run of conflux proxy sets the machine's whole list of published ports, so several services are one command with every spec in it.
$ sudo conflux proxy 5432=127.0.0.1:5432 6379=127.0.0.1:6379 80=127.0.0.1:3000
The list is kept in proxies in the configuration file, which can be edited by hand and applied with sudo conflux start. To try a port without keeping it, sudo conflux anchorctl proxy -add 9000=127.0.0.1:9000 serves it until the anchor next restarts.
When it does not connect
| Symptom | Check |
|---|---|
| Refused, or times out at once | sudo conflux anchorctl proxy on the server lists what the anchor serves, with live and failed connections and the backend's last error. |
| Nothing at all from the client | The DATA column in sudo conflux peers on the client. A no there is a taint set that is not contained. A no on every peer can also mean the client is blocked. |
| The page loads, then sends you to localhost | The app's own external address setting, named in its recipe: root_url, ROOT_URL, trusted_domains and the like. |
| Works on the server, not from a client | The service listens somewhere the backend spec does not name, or a Docker port is published somewhere other than 127.0.0.1. |
The rest is in conflux troubleshooting.