Skip to content

Installation

Complete guide to provisioning a Portlama server from a fresh DigitalOcean droplet in under 10 minutes.

In Plain English

This guide walks you through creating a cheap cloud server and running a single command that turns it into a secure relay for your local web apps. By the end, you have a browser-accessible admin panel protected by a client certificate — no SSH required after setup.

Prerequisites

Before you begin, make sure you have:

  • A DigitalOcean account (or any VPS provider offering Ubuntu 24.04)
  • A local terminal with SSH and SCP available (macOS Terminal, Linux shell, or Windows PowerShell)
  • A modern browser (Chrome, Firefox, Safari, or Edge) for importing the client certificate
  • Approximately 10 minutes of uninterrupted time

Step-by-Step

1. Create a DigitalOcean Droplet

  1. Log in to DigitalOcean.
  2. Click Create and select Droplets.
  3. Choose these settings:
SettingValue
RegionChoose the closest to your users
ImageUbuntu 24.04 (LTS) x64
SizeBasic, Regular, $4/mo (512 MB RAM, 1 vCPU, 10 GB SSD)
AuthenticationSSH keys (recommended) or password
HostnameSomething memorable, e.g. portlama-relay
  1. Click Create Droplet.
  2. Wait for the droplet to boot. Copy its public IP address from the dashboard.

The $4/month droplet is sufficient. Portlama is designed for a 512 MB RAM budget — all services together use approximately 245 MB, with a 1 GB swap file as a safety net.

2. SSH into the Droplet

Open your terminal and connect:

bash
ssh root@203.0.113.42

Replace 203.0.113.42 with your droplet's actual IP address.

If you used an SSH key during droplet creation, you connect immediately. If you chose a password, enter it when prompted.

Expected output:

Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.x.x-xxx-generic x86_64)
...
root@portlama-relay:~#

3. Run the Installer

Execute the Portlama installer:

bash
apt install -y npm
npx @lamalibre/create-portlama

The installer displays a confirmation banner before making any changes:

┌─────────────────────────────────────────────────────────────┐
│  Portlama Installer                                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  This will install Portlama on this machine.                │
│                                                             │
│  The following changes will be made:                         │
│                                                             │
│    * Reset UFW firewall (allow ports 22, 80, 443, 9292)     │
│    * Harden SSH (disable password authentication)           │
│    * Install fail2ban, Node.js 20, nginx, certbot           │
│    * Generate mTLS certificates for browser access          │
│    * Create portlama user and systemd service               │
│    * Deploy panel to /opt/portlama/                         │
│                                                             │
│  Designed for a fresh Ubuntu 24.04 droplet.                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

  Press Enter to continue or Ctrl+C to abort...

Press Enter to proceed. To skip the confirmation prompt, use npx @lamalibre/create-portlama --yes.

4. What the Installer Does

The installer runs six sequential phases. Each phase displays real-time progress:

Phase 1 — Environment checks

✔ Checking environment
  ✔ Verifying root access
  ✔ Detecting operating system → Ubuntu 24.04 LTS
  ✔ Detecting IP address → 203.0.113.42

The installer verifies you are running as root on Ubuntu 24.04 and detects the droplet's public IP address.

Phase 2 — OS hardening

✔ Hardening operating system
  ✔ Creating swap file → Swap file created and activated
  ✔ Configuring UFW firewall → UFW enabled with ports 22, 80, 443, 9292 allowed
  ✔ Installing and configuring fail2ban → fail2ban status: active
  ✔ Hardening SSH configuration → SSH hardened: key-auth only, root with keys only
  ✔ Installing system dependencies → System dependencies installed, nginx stopped

This phase creates a 1 GB swap file, configures the UFW firewall to allow only ports 22 (SSH), 80 (HTTP for Let's Encrypt), 443 (HTTPS), and 9292 (panel), installs fail2ban with SSH and nginx jails, hardens SSH to disable password authentication, and installs nginx, certbot, curl, and openssl.

Phase 3 — Node.js

✔ Installing Node.js 20
  ✔ Checking existing Node.js installation → Node.js not found, installing v20 LTS
  ✔ Installing NodeSource repository → NodeSource repository added
  ✔ Installing Node.js 20 LTS → Node.js installed
  ✔ Verifying Node.js installation → Node.js v20.x.x, npm 10.x.x

If Node.js 20 or later is already installed, this phase is skipped.

Phase 4 — mTLS certificates

✔ Generating mTLS certificates
  ✔ Creating PKI directory → PKI directory: /etc/portlama/pki
  ✔ Generating CA private key and certificate → CA key and certificate generated
  ✔ Generating client key and CSR → Client key and CSR generated
  ✔ Signing client certificate with CA → Client certificate signed and CSR removed
  ✔ Creating PKCS12 bundle → PKCS12 bundle created

This phase creates a private certificate authority (CA), signs a client certificate, and bundles it into a .p12 file for browser import. The CA certificate has a 10-year validity; the client certificate has a 2-year validity.

Phase 5 — nginx

✔ Configuring nginx
  ✔ Generating self-signed TLS certificate for IP access → Self-signed TLS certificate generated
  ✔ Writing mTLS snippet → mTLS snippet written
  ✔ Writing IP-based panel vhost → Vhost written
  ✔ Deploying certificate help page → Certificate help page deployed
  ✔ Enabling site and cleaning up defaults → Site enabled, default removed
  ✔ Validating and starting nginx → nginx is running and listening on port 9292

nginx is configured to listen on port 9292 with TLS and mTLS client certificate verification. A self-signed certificate is used for the IP-based connection (a proper Let's Encrypt certificate is issued later during onboarding when you configure a domain).

Phase 6 — Panel deployment

✔ Deploying Portlama panel
  ✔ Creating system user → Created system user: portlama
  ✔ Creating directory structure → Directories created
  ✔ Deploying panel-server → Panel server deployed
  ✔ Deploying panel-client → Panel client deployed from pre-built dist
  ✔ Writing panel configuration → Configuration written to /etc/portlama/panel.json
  ✔ Writing systemd service unit → Systemd service unit written
  ✔ Writing sudoers rules → Sudoers rules written and validated
  ✔ Starting panel service → Panel service running. Health: {"status":"ok"}

The panel server runs as the portlama system user with restricted sudoers rules, deployed to /opt/portlama/. Configuration lives in /etc/portlama/panel.json.

5. Read the Summary

After all phases complete, the installer prints a summary box:

╔════════════════════════════════════════════════════════════════╗
║                                                                ║
║   Portlama installed successfully!                             ║
║                                                                ║
╠════════════════════════════════════════════════════════════════╣
║                                                                ║
║   1. Download your client certificate:                         ║
║                                                                ║
║      scp root@203.0.113.42:/etc/portlama/pki/client.p12 .     ║
║                                                                ║
║   2. Import client.p12 into your browser:                      ║
║                                                                ║
║      macOS:  Double-click the file → Keychain Access           ║
║              → select "System" keychain (not "login")          ║
║              → enter the password below when prompted          ║
║              → find cert in System keychain → double-click     ║
║              → Trust → Always Trust                            ║
║                                                                ║
║      Linux:  Chrome → Settings → Privacy & Security            ║
║              → Security → Manage certificates → Import         ║
║                                                                ║
║      Windows: Double-click the file → Certificate Import       ║
║               Wizard → enter the password below                ║
║                                                                ║
║   3. Certificate password:                                     ║
║                                                                ║
║      <random-password-here>                                    ║
║                                                                ║
║   4. Open the Portlama panel:                                  ║
║                                                                ║
║      https://203.0.113.42:9292                                 ║
║                                                                ║
║      (Your browser will warn about the self-signed cert.       ║
║       This is expected — click "Advanced" → "Proceed")         ║
║                                                                ║
║   You can now disconnect from SSH.                             ║
║   The panel is running and accessible from your browser.       ║
║                                                                ║
╚════════════════════════════════════════════════════════════════╝

Save the certificate password. You need it to import the .p12 file into your browser.

6. Download the Client Certificate

Open a new terminal window on your local machine (not the SSH session) and run:

bash
scp root@203.0.113.42:/etc/portlama/pki/client.p12 .

This copies the certificate bundle to your current directory.

7. Import the Certificate into Your Browser

The .p12 file contains both your client certificate and private key, protected by the password shown in the summary.

macOS (Chrome, Safari, Firefox):

  1. Double-click client.p12.
  2. Keychain Access opens. Select the System keychain (not "login").
  3. Enter the certificate password when prompted.
  4. Find the imported certificate in the System keychain (look for "admin" under "My Certificates").
  5. Double-click the certificate, expand Trust, and set "When using this certificate" to Always Trust.
  6. Close the dialog and enter your macOS password to confirm.

If you see an error about "Local Items" — ignore it. The System keychain import is what matters.

Linux (Chrome):

  1. Open Chrome and navigate to chrome://settings/certificates.
  2. Click Import under "Your Certificates".
  3. Select client.p12 and enter the password.

Windows (Chrome, Edge):

  1. Double-click client.p12.
  2. The Certificate Import Wizard opens.
  3. Choose "Current User" and click Next.
  4. Enter the certificate password and click Next.
  5. Let Windows automatically select the certificate store.
  6. Click Finish.

8. Access the Panel

Open your browser and navigate to:

https://203.0.113.42:9292

Your browser warns about the self-signed TLS certificate. This is expected — the IP-based connection uses a self-signed cert because Let's Encrypt cannot issue certificates for IP addresses.

  • Chrome: Click "Advanced", then "Proceed to 203.0.113.42 (unsafe)".
  • Firefox: Click "Advanced", then "Accept the Risk and Continue".
  • Safari: Click "Show Details", then "visit this website".

Your browser then presents the client certificate. Select the "admin" certificate and click OK.

You see the Portlama onboarding wizard. Proceed to the Onboarding Guide for the next steps.

9. Disconnect SSH

You can now close your SSH session:

bash
exit

Everything from this point forward happens through the browser-based admin panel. SSH is never needed again for normal operation.

For Developers

Installer Internals

The installer (packages/create-portlama) is built with Listr2 for progress display and execa for shell commands. It runs as a single orchestrated pipeline:

main() → envTasks → confirmInstallation() → installTasks → printSummary()

Each task module exports a function that returns a Listr2 subtask list:

ModuleFilePurpose
hardenTaskssrc/tasks/harden.jsSwap, UFW, fail2ban, SSH, apt packages
nodeTaskssrc/tasks/node.jsNode.js 20 LTS via NodeSource
mtlsTaskssrc/tasks/mtls.jsCA, client cert, PKCS12 bundle
nginxTaskssrc/tasks/nginx.jsSelf-signed TLS, mTLS snippet, vhost
panelTaskssrc/tasks/panel.jsSystem user, deploy, config, systemd, sudoers

Idempotency

Every task includes skip guards. Running the installer a second time detects existing state and skips completed steps:

  • Swap already active: skips creation
  • Node.js 20+ installed: skips NodeSource setup
  • mTLS CA and client.p12 exist: skips all certificate generation
  • UFW active with correct rules: skips firewall configuration
  • SSH already hardened: skips SSH changes
  • Existing panel.json: merges rather than overwrites

Directory Structure on the Server

After installation, the server has these Portlama directories:

PathOwnerPurpose
/etc/portlama/portlama:portlamaConfiguration and state files
/etc/portlama/pki/root:root (700)CA, client cert, server cert, .p12
/etc/portlama/panel.jsonportlama:portlamaPanel configuration
/opt/portlama/panel-server/portlama:portlamaFastify backend
/opt/portlama/panel-client/portlama:portlamaReact frontend (built)
/var/www/portlama/www-data:www-dataStatic site files

CLI Flags

FlagEffect
--help, -hPrint help and exit
--yes, -ySkip confirmation prompt
--skip-hardenSkip swap, UFW, fail2ban, and SSH hardening
--devAllow private/non-routable IP addresses
--uninstallPrint manual removal guide and exit

Systemd Service

The panel runs as portlama-panel.service:

ini
[Service]
User=portlama
Group=portlama
WorkingDirectory=/opt/portlama/panel-server
ExecStart=/usr/bin/node src/index.js
Environment=NODE_ENV=production
Environment=CONFIG_FILE=/etc/portlama/panel.json
Restart=always
RestartSec=5

The service includes security hardening: ProtectHome=true, PrivateTmp=true, and ReadWritePaths for the config and static site directories. NoNewPrivileges is intentionally omitted because the panel needs sudo for provisioning (Chisel, Authelia, certbot, nginx, systemctl). Access is restricted via fine-grained sudoers rules in /etc/sudoers.d/portlama.

Quick Reference

ItemValue
Install commandnpx @lamalibre/create-portlama
Minimum VPSUbuntu 24.04, 512 MB RAM
Required ports22 (SSH), 80 (HTTP/Let's Encrypt), 443 (HTTPS), 9292 (panel)
Certificate location/etc/portlama/pki/client.p12
Download commandscp root@<ip>:/etc/portlama/pki/client.p12 .
Panel URLhttps://<ip>:9292
Config file/etc/portlama/panel.json
Service nameportlama-panel
Service logsjournalctl -u portlama-panel -f
Re-run safeYes (idempotent)
Total install time~5-10 minutes

Released under the PolyForm Noncommercial License 1.0.0