Documentation navigation

Wayfarer

Advanced native and manual deployment

This page is for administrators who deliberately run Wayfarer directly on the host instead of using the supported Compose/wayfarerctl production path.

Native deployment gives you full control over systemd, PostgreSQL, Nginx and browser/runtime provisioning, but you also own their compatibility and recovery. For a new production installation, prefer guided self-hosting.

Automated migration from an existing native/systemd installation to Compose is not currently supported. Do not treat wayfarerctl update as a native migration tool.

Native deployment responsibilities

You own:

Production web startup intentionally does not migrate, seed or create/reset the administrator.

Platform and dependencies

The current project baseline is .NET 10 and Node 24. Native/manual deployment uses PostgreSQL 18 with PostGIS.

Ubuntu 24.04 is the primary maintained Linux baseline, but a native operator must verify package names and runtime dependencies for the chosen distribution.

A practical native host needs:

Before HTTPS acquisition, update the host’s CA package through the distribution’s signed package manager, including existing hosts. Noble requires at least 20260816ubuntu0.24.04.1; Bookworm requires at least 20250419~deb12u2. Other distributions require their incident-corrected package. Native .NET and self-contained operator/recovery executables use host trust on Linux; replacing the executable does not replace that store. Run bash deployment/check-trust.sh on runtime hosts and bash deployment/check-trust.sh --node on build/provisioning hosts before NuGet, Cargo, npm or browser downloads. The check rejects G3 and requires ISRG Root X1 as a positive control; custom trust policies require their own qualification. Existing Node/npm installations are checked too; repair an old installation rather than accepting it merely because it is on PATH.

Before enabling history writers/reads after an upgrade, run history-data status with the same executable, runtime and globalization configuration as the service. R1 history qualification admits loaded .NET 10.0.13/10.0.14 with the reviewed artifacts and ICU profile, independent of host paths or machine identity. A package version or SDK label does not identify the runtime actually executing the service. Other globalization/runtime profiles need qualification; failure stays local to affected history operations and cannot be repaired by rebuilding. This admission does not waive #915 security-release requirements. Stop old processes and follow history maintenance: R1’s real timezone and parser changes require rebuilding both old generations, whereas qualified compatible servicing preserves rows/checkpoints. Inspect status before preparing; no startup/backfill scheduler performs this work.

Create a service identity

Use a dedicated unprivileged account, for example:

sudo useradd -r -s /usr/sbin/nologin wayfarer

If you intentionally need a home directory for service-owned state, create it explicitly and restrict it to that account.

Do not run the web process as root.

Prepare PostgreSQL

Create a dedicated database and application role, then enable the required extensions.

Representative SQL:

CREATE DATABASE wayfarer;
CREATE USER wayfareruser WITH PASSWORD 'replace-with-a-protected-secret';
GRANT ALL PRIVILEGES ON DATABASE wayfarer TO wayfareruser;

\c wayfarer
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS citext;
GRANT ALL ON SCHEMA public TO wayfareruser;

Store the connection string through protected host configuration, not in committed appsettings*.json.

For systemd, ASP.NET Core environment variables use double underscores:

Environment="ConnectionStrings__DefaultConnection=Host=localhost;Database=wayfarer;Username=wayfareruser;Password=..."

Build and publish

From a trusted source checkout on the build host:

dotnet restore
dotnet tool restore
dotnet frontend build
npm ci
npm run build
dotnet publish Wayfarer.csproj -c Release -o /absolute/publish

The native Linux AMD64/ARM64 build also bundles image-helper/wayfarer-image and its complete third-party notices. Build on the target architecture and retain the helper executable mode; do not grant the service write access to its payload. See the image helper contract for prerequisites, containment, the draft-only upstream pin and rollback. Native Windows remains unqualified; use WSL2 pending the required maintainer decision before merge.

Validate the published output, not only a source-tree Production run. Published static-web assets and the Trip Editor manifest must be present.

Deploy the published tree to a root-controlled location such as /var/www/wayfarer, readable by the service identity.

Prepare runtime storage

The Linux Production defaults use:

/var/lib/wayfarer
/var/cache/wayfarer
/var/log/wayfarer
/tmp/wayfarer

The service user needs appropriate access to the runtime-owned portions. In particular, prepare the Uploads/import path beneath the selected DataRoot before startup.

Prefer explicit ASP.NET Core environment variables or a protected production configuration source when changing these roots.

Legacy native storage

Older native installations can still contain same-host compatibility references beneath historical Uploads, TileCache or ImageCache trees.

Do not delete those directories merely because newer writes use externalized storage roots. Old database rows can still reference legacy upload paths and cache state can still be retired/replaced through normal application behavior.

Cross-host/native-to-Compose conversion of those references requires deliberate migration; startup does not rewrite them for you.

Data Protection key ring

Personal provider credentials and other durable protected payloads require the complete active ASP.NET Core Data Protection key ring.

Native resolution follows this order:

  1. nonblank DataProtection:KeyRingPath / DataProtection__KeyRingPath;
  2. otherwise Storage:DataRoot/data-protection;
  3. if only the previous platform default contains ordinary key-*.xml files, keep using that ring in place;
  4. if both distinct defaults contain keys, fail closed.

Do not merge key rings or choose one by file age.

The stable Data Protection application name is Wayfarer. Existing explicit native overrides such as:

/home/wayfarer/.aspnet/DataProtection-Keys

remain authoritative until you intentionally migrate them.

Back up PostgreSQL and the complete resolved active key ring together. Restore the ring with the service user’s expected ownership and restrictive permissions before starting the application. Database-only recovery can leave stored provider credentials permanently unreadable.

Prepare legacy protected credentials before stable-identity activation

Older native installations can contain provider credentials protected under the previous content-root-derived application identity. The current stable runtime reads only the stable companion identity.

Before activating a release that expects the stable identity:

  1. stop/quiesce the old service while it is still running from its original readable content root and key-ring configuration;
  2. back up PostgreSQL and the complete resolved active key ring together;
  3. apply the additive credential-companion schema migration if it is still absent;
  4. from that same service account, working/content root and ring configuration, run:
dotnet Wayfarer.dll data-protection prepare-stable-identity
dotnet Wayfarer.dll data-protection status

Require exit 0 from status and Activation-ready: True before starting the stable-identity web runtime. Then take another paired database + complete-ring recovery set.

If the database was already prepared, do not rerun preparation blindly. With the service stopped, run only:

dotnet Wayfarer.dll data-protection status

and require the intended ring plus activation-ready state.

These offline commands disable automatic key generation and do not contact providers. Preparation is transactional and fills only missing stable companions; it fails closed on unreadable, mismatched or inconsistent credential state.

If the old content-root identity is already lost, moving the key files to a new root does not recreate it. Restore the readable source identity or re-enter the affected credentials deliberately.

After stable-identity credential replacement or revocation, rollback to an older legacy reader may no longer be able to read those credentials. Recovery then needs the matching pre-change database + complete-ring set or credential re-entry.

Run maintenance before Production startup

From the published directory, under the same service identity/configuration used by the web process:

dotnet Wayfarer.dll database migrate
dotnet Wayfarer.dll database seed
dotnet Wayfarer.dll admin bootstrap <username> --stdin < /protected/password-file

For an existing administrator whose password must be recovered:

dotnet Wayfarer.dll admin reset <username> --stdin < /protected/password-file

Use protected stdin, not a password in argv or shell history.

The Production password policy requires at least 15 characters with uppercase, lowercase, a digit and a non-alphanumeric character.

Run the web service only after migrations/seeding/bootstrap complete successfully.

Provision Playwright Chromium

PDF generation uses the Chromium version matched to Wayfarer’s Microsoft.Playwright package.

The application does not download or install Chromium during startup or a PDF request.

After publishing the current source, use the explicit maintenance command in that published application. It calls the supported Playwright .NET installation API from the release’s own package and returns the tool’s exit status before any web, database or logging startup. No SDK or PowerShell is needed on the runtime host. Older published applications without this command must be updated to a release that includes it before using these instructions.

Install Linux OS libraries with root privileges, then download the matched bundle:

bash deployment/check-trust.sh --node
# Use an absolute patched Node path available to root; keep the release's own CLI.
provisioning_node="$(command -v node)"
sudo env PLAYWRIGHT_NODEJS_PATH="$provisioning_node" \
  dotnet /var/www/wayfarer/Wayfarer.dll playwright install-deps chromium
sudo env PLAYWRIGHT_BROWSERS_PATH=/opt/wayfarer-browsers \
  PLAYWRIGHT_NODEJS_PATH="$provisioning_node" \
  dotnet /var/www/wayfarer/Wayfarer.dll playwright install chromium

Append --dry-run to the install command to preview downloads. Use playwright install --with-deps chromium to combine dependency and browser installation with the required privileges. install-deps chromium uses the distribution package manager on supported Linux hosts, including the ALSA library that supplies libasound.so.2. Generated vendor installer scripts may be present in the publish tree; Wayfarer’s provisioning workflow does not execute them.

Give the service read/execute access to the browser bundle and configure:

Environment=PLAYWRIGHT_BROWSERS_PATH=/opt/wayfarer-browsers

Do not substitute an arbitrary system Chromium when the release expects a version-matched Playwright bundle.

Keep temporary browser profiles outside the publish tree.

Configure systemd

Representative service:

[Unit]
Description=Wayfarer
After=network.target postgresql.service

[Service]
User=wayfarer
WorkingDirectory=/var/www/wayfarer
ExecStart=/usr/bin/dotnet /var/www/wayfarer/Wayfarer.dll --urls http://127.0.0.1:5000
Restart=always
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=PLAYWRIGHT_BROWSERS_PATH=/opt/wayfarer-browsers
Environment="ConnectionStrings__DefaultConnection=Host=localhost;Database=wayfarer;Username=wayfareruser;Password=..."
Environment="TrustedProxy__Addresses__0=127.0.0.1"
Environment="AllowedHosts=wayfarer.example.com"
Environment="Application__ContactEmail=admin@example.com"

[Install]
WantedBy=multi-user.target

If the reverse proxy also connects over ::1, add the corresponding trusted proxy entry.

AllowedHosts should contain semicolon-separated exact public DNS hostnames. Do not use URL schemes, ports, wildcard hosts, localhost/private names or IP literals as public origin identities.

After editing the unit:

sudo systemctl daemon-reload
sudo systemctl enable wayfarer
sudo systemctl start wayfarer

Configure Nginx or another reverse proxy

The proxy should terminate HTTPS and forward to the local Kestrel endpoint.

Minimal Nginx shape:

server {
    listen 443 ssl http2;
    server_name wayfarer.example.com;

    client_max_body_size 100M;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
    }
}

Use your normal certificate automation.

Wayfarer owns application browser security headers. Do not add a blanket frame policy at the proxy that breaks intentional public Timeline/Trip embeds.

If you expose direct thumbnail routes through Nginx, keep the existing /thumbs/ proxy ordering ahead of generic image rules so Kestrel remains the authority for those responses.

First Production start

After maintenance commands and proxy configuration:

sudo systemctl start wayfarer
sudo systemctl status wayfarer
sudo journalctl -u wayfarer -n 100

Open the public HTTPS URL, sign in with the protected administrator created during bootstrap and enable 2FA.

Keep registration closed unless you intentionally operate an open instance.

Updating a native installation

Native updates are administrator-owned.

Before an update:

  1. stop Wayfarer writers;
  2. take a database backup;
  3. preserve Uploads;
  4. preserve the complete resolved active Data Protection key ring;
  5. retain the previous release/browser bundle until rollback compatibility is understood.

Build/publish the target release separately. Apply its pending migrations while the old web process is stopped, then start the target only after migration succeeds.

A safe outline is:

sudo systemctl stop wayfarer

# Build/publish target into a staging directory first.
# Run the target's maintenance command with Production configuration.
sudo -u wayfarer dotnet /absolute/staged/Wayfarer.dll database migrate

# Deploy the already-built output while preserving external durable storage and
# any retained legacy compatibility directories still referenced by this install.
sudo rsync -a --delete \
  --exclude 'Uploads' --exclude 'TileCache' --exclude 'ImageCache' \
  /absolute/staged/ /var/www/wayfarer/

# Apply the ownership/read permissions required by your native service layout.
# Keep any retained legacy Uploads/TileCache/ImageCache trees writable by the
# service account while they remain referenced.

sudo systemctl start wayfarer
sudo systemctl status wayfarer

Adjust ownership/read permissions so the service can execute/read the published tree but cannot rewrite deployment binaries.

Do not run an old application against a database after an incompatible migration merely because the old files still exist. Database rollback requires a compatible verified backup, not just a previous binary.

Native backup boundary

At minimum, keep a recovery set containing:

Capture these with writers stopped when you need a strong cross-component recovery point.

Caches, thumbnails, logs, browser temp and TLS certificates are not replacements for durable application data.

Native troubleshooting

Application:

sudo systemctl status wayfarer
sudo journalctl -u wayfarer -n 100

Database:

sudo systemctl status postgresql
psql -h localhost -U wayfareruser -d wayfarer

Proxy:

sudo nginx -t
sudo tail -n 100 /var/log/nginx/error.log

Browser/PDF:

Do not delete a shared browser bundle or Data Protection ring as a generic troubleshooting step.

Native-to-Compose boundary

The supported Compose operator does not import a native installation in place and wayfarerctl update does not convert native state.

A future migration must deliberately account for:

Until such a migration path is explicitly supported, keep native and Compose procedures separate.

Self-hosting guide · Operations · wayfarerctl reference

Back to top