Self-Hosting

Configure Phone Login & MFA

Set up phone login SMS providers, OTP settings, and multi-factor authentication for self-hosted Supabase with Docker.


This guide covers the server-side configuration for phone login and multi-factor authentication (MFA) on a self-hosted Supabase instance running with Docker Compose.

For client-side implementation, see Phone Login and Multi-Factor Authentication.

Before you begin#

You need:

  • A working self-hosted Supabase installation. See Self-Hosting with Docker.
  • An account with an SMS provider (e.g., Twilio)

Phone auth is enabled by default in the Docker setup (ENABLE_PHONE_SIGNUP=true in .env). However, without an SMS provider configured, the Auth service has no way to deliver OTP codes.

SMS provider configuration#

The default .env.example and docker-compose.yml include commented-out SMS provider placeholders. The example below uses Twilio - you'll need a Twilio account with an account SID, auth token, and message service SID. See Twilio's documentation for how to obtain these credentials.

To enable SMS delivery:

Step 1: Uncomment and configure the settings in .env#

1
SMS_PROVIDER=twilio
2
SMS_OTP_EXP=60
3
SMS_OTP_LENGTH=6
4
SMS_MAX_FREQUENCY=60s
5
SMS_TEMPLATE=Your code is {{ .Code }}
6
7
## Twilio credentials
8
SMS_TWILIO_ACCOUNT_SID=your-account-sid
9
SMS_TWILIO_AUTH_TOKEN=your-auth-token
10
SMS_TWILIO_MESSAGE_SERVICE_SID=your-message-service-sid

Step 2: Uncomment the matching lines in docker-compose.yml#

Uncomment the GOTRUE_SMS_* lines in the auth service's environment block:

1
GOTRUE_SMS_PROVIDER: ${SMS_PROVIDER}
2
GOTRUE_SMS_OTP_EXP: ${SMS_OTP_EXP}
3
GOTRUE_SMS_OTP_LENGTH: ${SMS_OTP_LENGTH}
4
GOTRUE_SMS_MAX_FREQUENCY: ${SMS_MAX_FREQUENCY}
5
GOTRUE_SMS_TEMPLATE: ${SMS_TEMPLATE}
6
GOTRUE_SMS_TWILIO_ACCOUNT_SID: ${SMS_TWILIO_ACCOUNT_SID}
7
GOTRUE_SMS_TWILIO_AUTH_TOKEN: ${SMS_TWILIO_AUTH_TOKEN}
8
GOTRUE_SMS_TWILIO_MESSAGE_SERVICE_SID: ${SMS_TWILIO_MESSAGE_SERVICE_SID}

Step 3: Restart the auth service#

1
docker compose up -d --force-recreate --no-deps auth

Step 4: Verify#

1
docker compose exec auth env | grep GOTRUE_SMS

Confirm your provider and credentials appear in the output.

OTP settings#

Expiration#

Set SMS_OTP_EXP in .env (value is in seconds):

1
# Set expiration to 5 minutes
2
SMS_OTP_EXP=300

And ensure GOTRUE_SMS_OTP_EXP: ${SMS_OTP_EXP} is uncommented in docker-compose.yml.

Length#

The default OTP length is 6 digits. You can set it to any value between 6 and 10:

1
SMS_OTP_LENGTH=8

Rate limiting#

SMS_MAX_FREQUENCY controls the minimum interval between SMS sends to the same phone number. The default is 60 seconds:

1
## Allow one SMS every 30 seconds
2
SMS_MAX_FREQUENCY=30s

Test OTPs for development#

To avoid sending real SMS during development, use SMS_TEST_OTP to map phone numbers to fixed OTP codes:

1
SMS_TEST_OTP=16505551234:123456,16505555678:654321

And uncomment GOTRUE_SMS_TEST_OTP: ${SMS_TEST_OTP} in docker-compose.yml.

When a test phone number requests an OTP, the Auth service skips SMS delivery and accepts only the mapped code. Other phone numbers continue to use the real SMS provider.

Multi-factor authentication (MFA)#

The Auth service supports three MFA factor types. Configure them by uncommenting variables in .env and the matching GOTRUE_MFA_* lines in docker-compose.yml.

App authenticator (TOTP)#

TOTP is enabled by default - users can enroll with apps like Google Authenticator or Authy without any additional configuration.

To disable TOTP:

1
MFA_TOTP_ENROLL_ENABLED=false
2
MFA_TOTP_VERIFY_ENABLED=false

Phone MFA#

Phone MFA is disabled by default (opt-in). It uses the same SMS provider configuration as phone login.

To enable:

1
MFA_PHONE_ENROLL_ENABLED=true
2
MFA_PHONE_VERIFY_ENABLED=true

Maximum enrolled factors#

By default, a user can enroll up to 10 MFA factors. To change this:

1
MFA_MAX_ENROLLED_FACTORS=5

Troubleshooting#

OTP expires too quickly#

The default SMS_OTP_EXP is 60 seconds. Increase it in .env:

1
SMS_OTP_EXP=300

Ensure GOTRUE_SMS_OTP_EXP: ${SMS_OTP_EXP} is uncommented in docker-compose.yml, then restart:

1
docker compose up -d --force-recreate --no-deps auth

SMS not being delivered#

Check the auth container logs for errors:

1
docker compose logs auth --tail 50

Verify provider credentials reach the container:

1
docker compose exec auth env | grep GOTRUE_SMS

Common causes:

  • Provider credentials are in .env but the matching GOTRUE_SMS_* line is still commented out in docker-compose.yml
  • Provider credentials are wrong
  • Phone number format is wrong (use E.164 format: +1234567890)

Variables are configured in .env but not working#

Configuration variables from .env are not automatically available inside the container unless there's a matching passthrough definition in docker-compose.yml. Check, e.g., for:

1
docker compose exec auth env | grep -E 'GOTRUE_SMS|GOTRUE_MFA'

After changing the configuration environment variables, recreate the Auth service container:

1
docker compose up -d --force-recreate --no-deps auth

Rate limit errors#

If users see "rate limit exceeded" errors, check SMS_MAX_FREQUENCY (minimum interval between sends) and the global rate limit GOTRUE_RATE_LIMIT_SMS_SENT (default: 30 per hour).

Additional resources#