xmrcloud
menu

doc: host-i2p-eepsite

Host an I2P eepsite on a XmrCloud VPS — i2pd install, HTTP server tunnel, the .b32.i2p address, optional .i2p name

published 2026-07-14 updated 2026-07-14 intermediate 13 min read

  • i2p
  • i2pd
  • eepsite
  • hidden-service
  • vps

abstract

abstract

Procedure for hosting an anonymous website (an eepsite) over I2P on the smallest offshore XmrCloud VPS, using i2pd — the lightweight C++ router that fits a 2 GB box far better than the Java stack. Covers the upstream-repo install of i2pd, a webserver bound to localhost, the exact tunnels.conf HTTP server tunnel that points I2P at it, reading the permanent .b32.i2p address out of the web console, and the optional step of registering a human-readable .i2p name through reg.i2p / stats.i2p. An eepsite never touches a clearnet IP and is low-resource — it fits the vps-1 tier.

Scope and assumptions

This is the operational walk-through for hosting an eepsite — a website reachable only inside the I2P network, at a .b32.i2p (or human-readable .i2p) address — on a XmrCloud VPS. I2P is a fully peer-to-peer overlay: unlike Tor, where your traffic exits through a small set of relays, every I2P router forwards traffic for others, and a “server tunnel” (the eepsite) is announced into the network database rather than published to a directory authority. The end state is a webserver bound to 127.0.0.1, an i2pd server tunnel that forwards inbound I2P streams to it, and a permanent I2P destination whose address is derived from a keypair you hold on the box. [i2pd — anonymous websites tutorial]

This doc uses i2pd, the C++ router, not the reference Java I2P router. On a VPS the choice is not close: i2pd is a single native daemon with a resident set in the low tens of MB, no JVM, and a plain .conf file surface. The Java router is heavier and oriented at desktop use. For overlay-network trade-offs generally — I2P vs Tor vs Lokinet — see /vs/i2p-vs-tor-vs-lokinet.

Baseline assumptions:

  • Debian 12 (bookworm) on a XmrCloud VPS. Package steps are identical on Ubuntu 22.04 / 24.04 LTS.
  • The brand’s hardened-by-default baseline is in place. See harden-sshd and kernel-hardening-checklist.
  • An eepsite is genuinely low-resource — a router plus a static site is a fraction of a core and a few hundred MB of RAM. The smallest vps-1 tier is sized for exactly this. If you want a router that also carries the network’s DHT, that is a floodfill, a different role — see /node/i2p-node and setup-i2p-floodfill.

Because the eepsite is a privacy-infrastructure box, the operator generally wants the VPS itself unlinkable to their identity — provisioned without KYC and paid for in a way that leaves no billing trail. That is the point of paying in Monero; see /why-monero and /payments.

1. Install i2pd from the upstream repo

The distro i2pd lags upstream and the network moves; install from PurpleI2P’s own repo. On Debian, the maintainers ship a helper that adds the signed repo for your release:

apt update
apt install -y apt-transport-https gnupg ca-certificates wget

# Adds the signed PurpleI2P repo for this Debian/Ubuntu release.
wget -q -O - https://repo.i2pd.xyz/.help/add_repo | bash -s -

apt update
apt install -y i2pd

On Ubuntu you may prefer the maintainers’ PPA instead of the repo script: add-apt-repository ppa:purplei2p/i2pd, then apt update && apt install -y i2pd. [i2pd — install (Debian/Ubuntu)]

The Debian package lays down its config under /etc/i2pd/ (i2pd.conf, tunnels.conf, and /etc/default/i2pd for daemon args), runs as an unprivileged i2pd user, and keeps its data — router info, and your eepsite keys — in /var/lib/i2pd/. Enable and start it:

systemctl enable --now i2pd.service

Confirm the router is up and note its version:

i2pd --version
i2pd version 2.55.0 (0.9.65)

2. Let the router integrate before you publish anything

A freshly-installed router has an empty netDb and no tunnels. Give it a few minutes to reseed and build exploratory tunnels before you expect the eepsite to be reachable. The web console is the honest signal that the router is healthy:

ss -tlnp | grep 7070

i2pd binds its web console to 127.0.0.1:7070 by default. It is NOT exposed to the public IP, and must not be — do not add a 0.0.0.0 bind. To view it from your laptop, forward it over SSH rather than opening the port:

ssh -L 7070:127.0.0.1:7070 root@eepsite-01

Then open http://127.0.0.1:7070/ locally. The main page shows Network status: OK and a climbing count of active tunnels once the router has integrated. Wait for that before continuing.

3. Bind a webserver to localhost

I2P forwards an inbound eepsite stream to a plain TCP service on the box; that service MUST listen on 127.0.0.1 only. A clearnet listener serving the same content on the public IP defeats the entire point — anyone who fetches both can correlate them. Install nginx and bind one static site to localhost:

apt install -y nginx

Drop this into /etc/nginx/sites-available/eepsite and enable it:

# /etc/nginx/sites-available/eepsite
# Eepsite origin — bind to localhost only. i2pd forwards the I2P stream here.
server {
    listen      127.0.0.1:8080;
    server_name _;

    server_tokens off;
    root  /var/www/eepsite;
    index index.html;

    # No access logging — an eepsite has no legitimate need for visitor logs,
    # and any we keep become a correlation surface if the box is seized.
    access_log off;
    error_log  /var/log/nginx/eepsite-error.log warn;

    location / {
        try_files $uri $uri/ =404;
    }
}
mkdir -p /var/www/eepsite
cat > /var/www/eepsite/index.html <<'EOF'
<!doctype html>
<html><head><meta charset="utf-8"><title>eepsite</title></head>
<body><pre>eepsite operational over i2p</pre></body></html>
EOF

ln -sf /etc/nginx/sites-available/eepsite /etc/nginx/sites-enabled/eepsite
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl reload nginx

Verify nginx is bound to 127.0.0.1:8080 ONLY — if you see 0.0.0.0:80 the stock default site is still enabled:

ss -tlnp | grep nginx
LISTEN 0  511  127.0.0.1:8080  0.0.0.0:*  users:(("nginx",pid=1234,fd=6))

4. Configure the HTTP server tunnel in tunnels.conf

This is the step that turns the localhost webserver into an eepsite. Edit /etc/i2pd/tunnels.conf and add one section for the site. The http tunnel type is a server tunnel specialised for eepsites — it rewrites the HTTP Host: header to the destination i2pd serves. [i2pd — I2P tunnels configuration (tunnels.conf)]

# /etc/i2pd/tunnels.conf — one section per eepsite.
[eepsite]
type = http
# host + port = the LOCALHOST service i2pd forwards inbound streams to.
host = 127.0.0.1
port = 8080
# keys = the file (under the i2pd datadir, /var/lib/i2pd) that stores this
# destination's long-term keypair. Your .b32.i2p address is derived from it.
# If the file does not exist yet, i2pd creates it on first start; if you
# leave `keys` empty, i2pd generates TRANSIENT keys that change on every
# restart — never do that for a site you want to keep an address for.
keys = eepsite.dat
# inport = the port INSIDE I2P the destination listens on. 80 is conventional
# for an eepsite so visitors can use a bare http://<addr>.b32.i2p URL.
inport = 80
# signaturetype 7 = Ed25519 (the current default; pin it explicitly).
signaturetype = 7

Reload i2pd so it reads the new tunnel. A running i2pd re-reads tunnels.conf on SIGHUP without dropping the router’s netDb; a full restart also works:

systemctl reload i2pd    # sends SIGHUP; or: systemctl restart i2pd

The keypair now exists at /var/lib/i2pd/eepsite.dat. Back it up OFFLINE — losing it means losing the address permanently, and anyone who holds it can impersonate your eepsite.

5. Read the permanent .b32.i2p address

i2pd derives your eepsite’s address from the keypair and exposes it in the web console. Open http://127.0.0.1:7070/?page=i2p_tunnels (over the SSH forward from step 2) and find your [eepsite] under Server tunnels — the long base32 string ending in .b32.i2p next to it is your permanent address. [i2pd — anonymous websites tutorial (finding the b32)]

That base32 address is immediately usable — hand it to anyone with an I2P router and they can reach the site (allow a few minutes for the leaseset to propagate through the netDb on first publish). To sanity-check it yourself, point a local I2P router’s HTTP proxy (i2pd’s own is 127.0.0.1:4444) at it:

curl -x http://127.0.0.1:4444 http://<your-address>.b32.i2p/
<!doctype html>
<html>...eepsite operational over i2p...

If it times out, the leaseset has not propagated yet — wait and retry. If it never resolves, re-check that the [eepsite] tunnel shows up under Server tunnels in the console and that nginx is answering on 127.0.0.1:8080.

6. (Optional) register a human-readable .i2p name

A .b32.i2p address is unmemorable by design. I2P has no global DNS; instead, address books are subscription-based and names propagate through registrars like reg.i2p and stats.i2p. Registering a name is optional and never a security control — the base32 is the cryptographic identity; the name is a convenience that a visitor’s address book maps to it.

The registration flow uses regaddr from the i2pd-tools package: you sign an assertion binding your chosen name.i2p to your destination’s public key with the eepsite’s private key, then submit the resulting authentication string to ONE registrar (reg.i2p OR stats.i2p, not both). [I2P — naming and the address book]

# Build i2pd-tools (no Debian package), then sign the name→key assertion.
git clone https://github.com/PurpleI2P/i2pd-tools.git /opt/i2pd-tools
cd /opt/i2pd-tools && make
./regaddr /var/lib/i2pd/eepsite.dat yoursite.i2p > /tmp/yoursite.authstring

Submit the contents of /tmp/yoursite.authstring to the registrar’s web form (reachable over I2P). Propagation to most users’ address books takes on the order of a week — the base32 keeps working the whole time, so nothing is gated on the name landing.

Next steps

The eepsite is live. In order:

  1. Hand out the .b32.i2p address (and the .i2p name once it propagates) over a channel your audience already trusts. I2P has no search-and-index expectation — announce to the people who need it.
  2. If you run dynamic content behind the eepsite, keep the same discipline as any hidden service: the application binds to 127.0.0.1, never to a public interface, and the process boundary is namespaced.
  3. If you also want to contribute routing capacity — a floodfill that carries the network’s address database — that is a separate role on its own box. Read setup-i2p-floodfill and pick the /node/i2p-node tier.
  4. If your audience is split across overlays, note that hosting the same content as a Tor onion is a parallel, well-trodden path — see provision-tor-hidden-service, and run-obfs4-bridge if you want to help censored users reach Tor at all.

An eepsite is one of the cheapest privacy-preserving things you can host: no clearnet IP, no directory authority, a permanent address you control. Run it on its own vps-1 and leave it alone.

cd /docs