Deployment guide

Self-host Logto on a VPS: your own authentication layer

Deploy on a VPS Cloud →

Tutorial

Development

Self-host Logto on a VPS: your own authentication layer

Hosted authentication services charge per monthly active user and store your users' login data on their own servers. Logto is the open-source alternative: a complete OIDC/OAuth 2.0 authentication platform you deploy on your own VPS and control end to end. One Docker Compose file, two containers (Logto and PostgreSQL 17), and your applications have production-ready authentication — sign-up, social login, MFA, role-based access control — with no per-user cost.

10 min read10 steps
Contents· What Logto actually is — and what it is not1/11
  1. 01What Logto actually is — and what it is not
  2. 02Prerequisites
  3. 03How Logto works in production
  4. 04What Logto provides out of the box
  5. 05Docker Compose deployment step by step
  6. 06Logging in for the first time
  7. 07Integrating Logto into a Next.js application
  8. 08Enabling Google social login
  9. 09Logto, Authentik, Authelia, Keycloak — which one to choose?
  10. 10Common troubleshooting
  11. 11Backup strategy

What Logto actually is — and what it is not

Logto is an auth SDK platform: you integrate it into your application's code via a 15-line SDK snippet, and it handles the complete user lifecycle on your own server. This is different from the two other authentication tools in the ServOrbit catalogue:

- Authelia adds MFA and SSO *in front of* existing apps via forward auth, without changing their code — it protects apps that have no login at all.
- Authentik is a full identity provider for centralising SSO across your self-hosted tools (Grafana, Gitea, Nextcloud).
- Logto replaces Auth0 or Clerk: you call its SDK from your React, Python, or Go application, and it manages user accounts, sessions, social connectors, and tokens.

If you're building a product where users create accounts, Logto is the right tool.

Prerequisites

Before deploying Logto 1.19+, verify the following on your ServOrbit VPS:

- RAM: at least 2 GB (4 GB recommended if you expect more than 5,000 active users). Logto runs on Node.js 20 LTS and shares the same instance as PostgreSQL 17.
- CPU: 1 vCPU is enough to get started, but 2 vCPU avoids login latency under load.
- Disk: reserve at least 10 GB free — PostgreSQL allocates WAL logs and active sessions accumulate quickly with the default audit logging enabled.
- Docker ≥ 24.0 and Docker Compose ≥ 2.20 — pre-installed on all ServOrbit VPS plans.
- PostgreSQL 17 minimum (included in the official Logto image) — older versions lack the required extensions.
- A domain name or subdomain pointing to your VPS and resolving publicly: Logto bakes this domain into every OIDC token it issues. It must be set before the first start and cannot be changed without resetting all user data.

How Logto works in production

Logto starts two containers: db (PostgreSQL 17 for users, apps, sessions, audit logs) and logto (Node.js serving the OIDC endpoint on port 3001 and the admin console on port 3002). The nginx reverse proxy on your VPS forwards HTTPS traffic on your domain to port 3001 — that's the endpoint your applications authenticate against. Port 3002 (admin console) is bound to localhost only and is accessible only via SSH tunnel, which keeps your user database management off the public internet.

What Logto provides out of the box

  • OIDC/OAuth 2.0 compliant tokens — signed JWTs that any standard library (passport.js, python-jose, go-oidc) can verify without custom code.
  • Social login connectors — enable Google, GitHub, Apple, Microsoft, Discord and 30+ providers from the admin console in one click; each requires a 5-minute OAuth App setup on the provider side.
  • Passwordless authentication — send magic links (email) or OTP codes (SMS via Twilio, SendGrid, Mailgun) without maintaining a separate notification service.
  • MFA — TOTP (Google Authenticator, Authy), WebAuthn/FIDO2 hardware keys, and backup codes, enforced by policy from the admin console.
  • Role-based access control — define roles and permissions in the console; receive them as scope claims in the JWT and enforce them in your API with a single middleware line.
  • SDKs for 20+ frameworks — React, Next.js, Vue, Angular, React Native, Flutter, Python, FastAPI, Go, PHP, Laravel, .NET, Java, Spring Boot and more.

Docker Compose deployment step by step

  1. Create the working directory and Compose file

    Connect to your VPS via SSH and create the working directory:

    mkdir -p /opt/logto && cd /opt/logto

    Create the docker-compose.yml file with the following content. Replace auth.your-domain.com with your own domain:

    version: '3.9'
    services:
      db:
        image: postgres:17-alpine
        restart: unless-stopped
        environment:
          POSTGRES_DB: logto
          POSTGRES_USER: logto
          POSTGRES_PASSWORD: ${DB_PASSWORD}
        volumes:
          - logto_db:/var/lib/postgresql/data
      logto:
        image: svhd/logto:latest
        restart: unless-stopped
        depends_on:
          - db
        ports:
          - '127.0.0.1:3001:3001'
          - '127.0.0.1:3002:3002'
        environment:
          DB_URL: postgresql://logto:${DB_PASSWORD}@db:5432/logto
          ENDPOINT: https://auth.your-domain.com
          ADMIN_ENDPOINT: https://auth.your-domain.com:3002
          SECRET_VAULT_KEK: ${SECRET_VAULT_KEK}
    volumes:
      logto_db:
  2. Generate secrets and create the .env file

    Generate a strong database password and an AES-256 key:

    DB_PASSWORD=$(openssl rand -hex 32)
    SECRET_VAULT_KEK=$(openssl rand -hex 32)
    printf "DB_PASSWORD=%s\nSECRET_VAULT_KEK=%s\n" "$DB_PASSWORD" "$SECRET_VAULT_KEK" > .env
    chmod 600 .env

    Verify that .env contains two non-empty lines before proceeding.

  3. Configure nginx as a reverse proxy

    Create the vhost /etc/nginx/sites-available/logto:

    server {
        listen 443 ssl;
        server_name auth.your-domain.com;
        ssl_certificate /etc/letsencrypt/live/auth.your-domain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/auth.your-domain.com/privkey.pem;
    
        location / {
            proxy_pass http://127.0.0.1:3001;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto https;
        }
    }

    Enable the site and reload nginx:

    ln -s /etc/nginx/sites-available/logto /etc/nginx/sites-enabled/
    nginx -t && systemctl reload nginx
  4. Start the stack and verify

    Launch both containers:

    docker compose up -d

    Follow the logs for 60 seconds to confirm PostgreSQL is ready and Logto has completed its migrations:

    docker compose logs -f --tail=50

    You should see the line App is running at https://auth.your-domain.com — that is the signal the service is operational.

  5. Verify the OIDC discovery document

    From your local machine, check that the OIDC endpoint responds:

    curl -s https://auth.your-domain.com/.well-known/openid-configuration | python3 -m json.tool | head -10

    The response must contain issuer, authorization_endpoint and jwks_uri. If the command returns a 502 error, Logto has not yet completed its migrations — wait 30 seconds and try again.

  6. Set up automatic backups

    Add a cron job to export the database daily:

    crontab -e
    # Add the following line:
    0 3 * * * docker exec $(docker compose -f /opt/logto/docker-compose.yml ps -q db) pg_dump -U logto logto | gzip > /var/backups/logto_$(date +\%Y\%m\%d).sql.gz

    For automated, deduplicated, encrypted backups to S3 or Backblaze B2, mount the logto_db volume to a Backrest instance available in the ServOrbit catalogue.

Logging in for the first time

  1. Open the SSH tunnel to the admin console

    The admin console listens on port 3002, bound to localhost for security. From your local machine, open a tunnel:

    ssh -L 3002:127.0.0.1:3002 root@your-vps-ip

    Keep this terminal open for the duration of your admin session.

  2. Create the administrator account

    Open http://localhost:3002 in your browser. The first-run wizard appears — there are no default credentials. Enter an email address and a strong password, then click Create account. These credentials are yours for administering Logto; store them in a password manager.

  3. Explore the dashboard

    After creating the account, you land on the Logto dashboard. From here you can view the audit log, manage users, configure social connectors, and create Applications — the Logto entity that represents one of your projects.

  4. Create your first Application

    In the console: Applications → Create application. Choose the type that matches your project (Traditional Web, Single Page App, Native, or Machine-to-machine). Give it a name, then fill in the Redirect URI — the URL Logto redirects to after a successful login (for example https://my-app.com/api/logto/sign-in-callback for a Next.js application). Logto then generates an App ID and App Secret that you will use in your SDK.

Integrating Logto into a Next.js application

Install the SDK with npm install @logto/next, then create a logto.ts that exports a new LogtoClient({ … }) configured with endpoint (your Logto instance URL), appId and appSecret (from the Logto admin console), baseUrl (your application URL), cookieSecret and cookieSecure.

To protect a route, call getLogtoContext({ getAccessToken: true }) imported from @logto/next/server-component: it returns isAuthenticated and the user's claims. If isAuthenticated is false, redirect to /api/logto/sign-in; otherwise render the page from the claims you received.

That is the whole integration: fifteen lines of configuration, one import, one guard.

Enabling Google social login

In the Google Cloud Console, create an OAuth 2.0 Client ID (Web application type). Set the authorised redirect URI to https://auth.your-domain.com/callback/google. Copy the Client ID and Client Secret.

In the Logto admin console → Connectors → Social connectors → Google → Enable. Paste your Client ID and Secret, save. Google now appears as a sign-in option on your Logto sign-in page automatically — no code changes needed in your application.

Logto, Authentik, Authelia, Keycloak — which one to choose?

Scroll the table

SolutionPrimary use caseMinimum RAMSDK integration complexity
**Logto**Authentication for a product you are building (sign-up, JWTs, social connectors)2 GBLow — official SDK in 15 lines for React, Next.js, Python, Go, Flutter and 15+ more
**Authentik**Centralised SSO for your self-hosted tools (Grafana, Gitea, Nextcloud, Proxmox)2 GBMedium — configuration via Providers in the console, no application SDK
**Authelia**MFA and login portal in front of existing apps, without modifying their code256 MBNone — auth delegation via nginx header, no SDK integration
**Keycloak**Enterprise SSO, LDAP/AD federation, advanced multi-tenant scenarios4 GB minimumHigh — manual OIDC configuration, client libraries to set up per framework

For a SaaS product under development, Logto is the natural starting point. Keycloak suits organisations that already have an enterprise directory to integrate. Authelia and Authentik address infrastructure needs rather than application needs.

Common troubleshooting

Here are the four most common issues during a first Logto VPS installation and how to resolve them.

Admin console inaccessible (port 3002)

Port 3002 is intentionally bound to 127.0.0.1 — it is not accessible directly from the internet. If http://localhost:3002 does not respond, first verify your SSH tunnel is open (ssh -L 3002:127.0.0.1:3002 root@<IP>). Then check that the Logto container is running: docker compose ps. If the container is in Exit state, inspect the logs: docker compose logs logto --tail=50.

OIDC discovery 502

Logto runs its database migrations on first start — this takes between 30 and 90 seconds depending on the VPS. If the discovery document /.well-known/openid-configuration returns 502, nginx is forwarding requests before Logto is ready. Wait 90 seconds and try again. If the error persists, check that PostgreSQL is healthy: docker compose logs db --tail=20 — look for the line database system is ready to accept connections.

Redirect URI mismatch

Logto validates the redirect URI strictly: if the URI sent by your application does not match exactly what is declared in the Logto console (Application → Redirect URIs), the login fails with redirect_uri_mismatch. Verify you have registered the complete URL including the scheme (https://), the exact path, and the absence of a trailing slash if your SDK does not send one. Development URIs (http://localhost:3000/…) must be added separately in the console.

Fatal: cannot connect to db

If the Logto logs show Fatal: cannot connect to db, PostgreSQL was not yet ready when Logto tried to connect. The depends_on declaration in Docker Compose guarantees container startup order but not that PostgreSQL is accepting connections. Simply restart the Logto container: docker compose restart logto. It will reconnect as soon as PostgreSQL is ready.

Backup strategy

Logto stores all its state in the logto_db PostgreSQL volume. Back it up with a pg_dump cron job or mount the volume to a Backrest instance (also in the ServOrbit marketplace) for automated, deduplicated, encrypted backups to S3, Backblaze B2 or SFTP. Losing the logto_db volume means losing all user accounts, application configurations, and audit logs — back it up before any VPS migration.

Your auth layer, on your own VPS

Deploy Logto on a ServOrbit VPS — own your users' authentication data, drop per-user billing, and integrate with SDKs for React, Next.js, Python, Go, Flutter and more.