Best Practices for Securing Your Logto Deployment: A Complete Production Guide

Implement TLS termination, enable Helmet security headers, enforce PostgreSQL Row-Level Security, rotate secrets via environment variables, and apply rate limiting to authentication endpoints to harden your Logto deployment.

Logto is an open-source OIDC provider and identity management platform that handles sensitive authentication data, user credentials, and tokens. Because it serves as the central identity layer for your applications, securing your Logto deployment is critical for protecting user data and preventing unauthorized access. This guide covers production-ready hardening techniques derived directly from the logto-io/logto source code, including specific file paths and configurations you can implement today.

Terminate TLS at the Reverse Proxy

Always serve Logto over HTTPS by terminating TLS at a reverse proxy such as Nginx or a cloud load balancer before forwarding traffic to Logto services. This prevents network eavesdropping and man-in-the-middle attacks while keeping the application layer simple.

The official Docker Compose example shows the expected port exposure pattern, leaving HTTPS termination to the host infrastructure:

version: '3.8'
services:
  logto:
    image: logto/logto:latest
    environment:
      - DB_URL=postgres://postgres:strongpassword@db:5432/logto?sslmode=require
    ports:
      - "3001:3001"   # user API

      - "3002:3002"   # admin API (firewall this port)

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_PASSWORD: strongpassword

Configure your reverse proxy to forward only encrypted traffic and set the X-Forwarded-Proto header to https so Logto correctly generates absolute URLs in OIDC responses.

Enforce Security Headers with Helmet Middleware

Logto uses Helmet middleware to set security headers that mitigate XSS, click-jacking, and other browser-based exploits. In packages/core/src/middleware/koa-security-headers.ts, the default configuration applies strict Content Security Policy (CSP), X-Frame-Options, and Referrer-Policy headers:

// packages/core/src/middleware/koa-security-headers.ts
import helmet, { type HelmetOptions } from 'helmet';

export const defaultExperienceSecurityHeaderSettings: HelmetOptions = {
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
    },
  },
  referrerPolicy: { policy: 'no-referrer' },
};

export const applySecurityHeaders = async (ctx: Koa.Context) => {
  await helmet(defaultExperienceSecurityHeaderSettings)(ctx.req, ctx.res);
};

As of version 3.13.0 noted in packages/core/CHANGELOG.md, CSP enforcement is mandatory—the reportOnly flag was removed, making content injection protections active by default. Customize these settings by modifying the exported HelmetOptions before mounting the Koa application.

Enable PostgreSQL Row-Level Security

Logto implements multi-tenancy with strict data isolation at the database level. Every tenant table is created with ENABLE ROW LEVEL SECURITY via the migration script in packages/schemas/tables/_after_each.sql:

-- packages/schemas/tables/_after_each.sql
ALTER TABLE ${name} ENABLE ROW LEVEL SECURITY;

This guarantees that a compromised tenant cannot read another tenant’s data. When the first migration runs (pnpm alteration deploy), PostgreSQL automatically enforces row-level policies that restrict rows to the current tenant ID. Ensure your database user does not have the BYPASSRLS attribute in production, and verify that migrations successfully apply the security clauses during deployment.

Secure Secret Management and Rotation

Store all sensitive credentials—including API secrets, JWT signing keys, and database URLs—via environment variables rather than configuration files. Logto expects variables like DB_URL and JWT_SECRET to be injected at runtime, as documented in packages/cli/README.md and implemented throughout the core packages.

Create a .env file based on the repository's .env.example that excludes real values from version control:

  • Rotate JWT_SECRET regularly and restart services to apply new keys
  • Use distinct credentials for the admin API versus the user-facing API
  • Enable sslmode=require in your PostgreSQL connection string to enforce encrypted database connections

Never commit production secrets to source control, and use your cloud provider's secret management system (AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault) to inject these values into container environments.

Separate Admin and User Ports

Logto runs user-facing APIs and administrative APIs on distinct ports—3001 for user APIs and 3002 for admin APIs. This architectural separation allows you to apply stricter network controls to privileged endpoints:

  • Expose port 3001 to public load balancers for authentication flows
  • Restrict port 3002 to internal networks or VPN access only
  • Apply additional authentication layers or IP whitelisting to the admin port at the reverse proxy level

Implement Rate Limiting and MFA

Protect against credential-stuffing and brute-force attacks by leveraging the rate-limiting definitions in packages/schemas/src/consts/message-rate-limit.ts:

// packages/schemas/src/consts/message-rate-limit.ts
export const verificationRateLimit = {
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // allow 5 failed attempts per window
};

Configure your reverse proxy or API gateway to enforce these limits on verification endpoints. Additionally, enforce Multi-Factor Authentication (MFA) for admin accounts through the account-center UI available at /account/security, which exposes MFA configuration options in the console.

Disable Unused Features and Adopt OAuth2 Standards

Reduce your attack surface by disabling features you do not use. Many capabilities (such as SSO or advanced MFA) are gated behind the isDevFeaturesEnabled flag in packages/core/src/feature-flags.ts. Keep these disabled in production unless specifically required.

For API security, Logto has transitioned to standard OAuth2 bearer token schemes as noted in the 2.0.43 changelog entry in packages/core/CHANGELOG.md. Ensure your OpenAPI definitions use this security schema to improve interoperability with API gateways and security scanners.

Summary

  • Use TLS termination at your reverse proxy to encrypt all traffic before it reaches Logto services.
  • Apply Helmet security headers via koa-security-headers.ts to prevent XSS and click-jacking with mandatory CSP enforcement.
  • Enable Row-Level Security in PostgreSQL through the _after_each.sql migration to isolate tenant data at the database level.
  • Inject secrets via environment variables and rotate JWT_SECRET and database credentials regularly.
  • Separate admin and user ports (3001 vs 3002) and firewall the admin interface.
  • Configure rate limiting using the constants in message-rate-limit.ts and enforce MFA for privileged accounts.
  • Disable development features via feature flags and use OAuth2 security schemas for API access.

Frequently Asked Questions

Should I use Logto's built-in HTTPS support or a reverse proxy?

Use a reverse proxy. The Logto source code is designed to run behind a TLS-terminating load balancer or reverse proxy (such as Nginx). The docker-compose.yml example exposes plain HTTP ports expecting the host to handle encryption. This approach centralizes certificate management and allows you to handle complex TLS configurations (like client certificates or specific cipher suites) at the edge.

How does Logto prevent cross-tenant data leaks?

Row-Level Security (RLS) in PostgreSQL. Every tenant table created during migration includes ENABLE ROW LEVEL SECURITY as defined in packages/schemas/tables/_after_each.sql. The runtime automatically applies tenant ID filters to queries, ensuring that users from one tenant cannot access data belonging to another tenant, even if application-level checks fail.

Update the JWT_SECRET environment variable and restart. Logto reads signing keys from environment variables at startup (see packages/cli/README.md for documentation). To rotate keys, generate a new secret, update your environment configuration in your secret management system, and perform a rolling restart of your Logto containers. The new keys take effect immediately for newly issued tokens.

Why are admin and user APIs on different ports?

For network segmentation and access control. Separating the admin API (port 3002) from the user-facing API (port 3001) allows you to apply stricter firewall rules to administrative endpoints. You can expose port 3001 publicly while restricting port 3002 to internal networks or VPN connections, minimizing the attack surface for privileged operations like tenant management and configuration changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →