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
scopeclaims 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
Create the working directory and Compose file
Connect to your VPS via SSH and create the working directory:
mkdir -p /opt/logto && cd /opt/logtoCreate the
docker-compose.ymlfile with the following content. Replaceauth.your-domain.comwith 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: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 .envVerify that
.envcontains two non-empty lines before proceeding.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 nginxStart the stack and verify
Launch both containers:
docker compose up -dFollow the logs for 60 seconds to confirm PostgreSQL is ready and Logto has completed its migrations:
docker compose logs -f --tail=50You should see the line
App is running at https://auth.your-domain.com— that is the signal the service is operational.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 -10The response must contain
issuer,authorization_endpointandjwks_uri. If the command returns a 502 error, Logto has not yet completed its migrations — wait 30 seconds and try again.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.gzFor automated, deduplicated, encrypted backups to S3 or Backblaze B2, mount the
logto_dbvolume to a Backrest instance available in the ServOrbit catalogue.
Logging in for the first time
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-ipKeep this terminal open for the duration of your admin session.
Create the administrator account
Open
http://localhost:3002in 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.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.
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-callbackfor a Next.js application). Logto then generates anApp IDandApp Secretthat 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
| Solution | Primary use case | Minimum RAM | SDK integration complexity |
|---|---|---|---|
| **Logto** | Authentication for a product you are building (sign-up, JWTs, social connectors) | 2 GB | Low — 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 GB | Medium — configuration via Providers in the console, no application SDK |
| **Authelia** | MFA and login portal in front of existing apps, without modifying their code | 256 MB | None — auth delegation via nginx header, no SDK integration |
| **Keycloak** | Enterprise SSO, LDAP/AD federation, advanced multi-tenant scenarios | 4 GB minimum | High — 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.