Skip to main content
SST provides built-in secrets management with encryption, per-stage values, and fallbacks. This guide shows you how to manage secrets securely across environments.

What are secrets?

Secrets are sensitive values that your application needs at runtime:
  • API keys (Stripe, Twilio, etc.)
  • Database passwords
  • OAuth client secrets
  • Private keys
  • Encryption keys
Never commit secrets to version control. Use SST’s secrets management instead.

Creating secrets

Define secrets in your sst.config.ts:
sst.config.ts
const stripeKey = new sst.Secret("StripeKey");
const dbPassword = new sst.Secret("DatabasePassword");

const api = new sst.aws.Function("MyApi", {
  handler: "src/api.handler",
  link: [stripeKey, dbPassword],
});
Secrets are defined like any other resource but their values are set separately using the CLI.

Setting secret values

Use the sst secret set command:
sst secret set StripeKey sk_test_abc123
This encrypts and stores the secret in your AWS account.

From a file

For multi-line secrets like RSA keys:
# Create a file
cat > key.pem <<EOF
-----BEGIN RSA PRIVATE KEY-----
MEgCQQCo9+BpMRYQ/dL3DS2CyJxRF+j6ctbT3/Qp84+KeFhnii7NT7fELilKUSnx
S30WAvQCCo2yU1orfgqr41mM70MBAgMBAAE=
-----END RSA PRIVATE KEY-----
EOF

# Set from file
sst secret set PrivateKey < key.pem

# Clean up
rm key.pem

Interactively

Omit the value to enter it interactively:
sst secret set StripeKey
Enter value: [hidden input]
Your input won’t be visible in the terminal or command history.

Using secrets

Access secrets in your functions with the SST SDK:
src/api.ts
import { Resource } from "sst";
import Stripe from "stripe";

const stripe = new Stripe(Resource.StripeKey.value, {
  apiVersion: "2023-10-16",
});

export const handler = async (event: any) => {
  const session = await stripe.checkout.sessions.create({
    // ...
  });
  
  return {
    statusCode: 200,
    body: JSON.stringify({ sessionId: session.id }),
  };
};
Secrets are decrypted automatically at runtime. You don’t need to handle decryption.

Per-stage secrets

Set different values for each stage:
# Development
sst secret set StripeKey sk_test_abc123 --stage dev

# Production
sst secret set StripeKey sk_live_xyz789 --stage production
Each stage can have its own secret values:
  • Development stage uses test API keys
  • Production stage uses live API keys
  • Each developer’s personal stage can have their own values

Fallback values

Set a default value that applies to all stages:
sst secret set ApiKey default-key-123 --fallback
Stages use fallback values when they don’t have their own:
  1. SST checks for stage-specific value (e.g., production)
  2. If not found, uses the fallback value
  3. If no fallback, the secret is undefined
Use fallbacks for secrets that are the same across environments, like third-party API keys that don’t have test modes.

Use case: Preview stages

Fallbacks are perfect for temporary preview stages:
# Set fallback for preview stages
sst secret set StripeKey sk_test_default --fallback

# Production uses its own value
sst secret set StripeKey sk_live_xyz789 --stage production
Now PR previews automatically use the test key without needing to set secrets for each PR.

Listing secrets

View all secrets for a stage:
sst secret list
Output:
# fallback
ApiKey=default-key-123

# my-app/dev
StripeKey=sk_test_abc123
DatabasePassword=***
List only fallback secrets:
sst secret list --fallback

Removing secrets

Remove a secret value:
sst secret remove StripeKey
Remove from a specific stage:
sst secret remove StripeKey --stage production
Remove fallback value:
sst secret remove StripeKey --fallback
Removing a secret doesn’t redeploy your functions. Run sst deploy or wait for the next deployment for changes to take effect.

Loading from files

Load multiple secrets from a file:
secrets.env
STRIPE_KEY=sk_test_abc123
SENDGRID_API_KEY=SG.abc123
DATABASE_PASSWORD=super-secret
sst secret load ./secrets.env
Supports .env format:
  • Key=value pairs
  • Comments with #
  • Quoted values
  • Multi-line values

Migrating secrets

Copy secrets from one stage to another:
# Export from staging
sst secret list --stage staging > secrets.env

# Import to production
sst secret load ./secrets.env --stage production

How secrets work

Encryption

Secrets are encrypted using AWS KMS:
  1. When you run sst secret set, the value is encrypted with a KMS key
  2. The encrypted value is stored in an S3 bucket in your AWS account
  3. At runtime, the value is decrypted and injected into your function
SST never stores unencrypted secrets. Everything is encrypted at rest and in transit.

Storage

Secrets are stored in:
  • S3 bucket — Named <app>-<stage>-secrets in your AWS account
  • Encrypted — Using AES-256-GCM with AWS KMS
  • Per-stage — Each stage has its own secrets bucket

Runtime access

In sst dev, secrets are:
  • Loaded from S3
  • Decrypted locally
  • Injected as environment variables
  • Available through Resource.SecretName.value
In deployed functions, secrets are:
  • Packaged into the function bundle (encrypted)
  • Decrypted at runtime
  • Available through Resource.SecretName.value

Console integration

Manage secrets through the SST Console:
  1. Go to your app in console.sst.dev
  2. Click Secrets
  3. Add, update, or remove secrets
  4. Changes are immediately available in sst dev
The Console provides a visual interface for managing secrets across all stages.

Best practices

Use descriptive names

Name secrets clearly:
// Good
const stripeKey = new sst.Secret("StripeKey");
const sendgridApiKey = new sst.Secret("SendgridApiKey");

// Avoid
const secret1 = new sst.Secret("Secret1");
const key = new sst.Secret("Key");

Separate development and production

Always use different secrets for different stages:
# Development - test keys
sst secret set StripeKey sk_test_abc123 --stage dev

# Production - live keys  
sst secret set StripeKey sk_live_xyz789 --stage production

Rotate secrets regularly

Update secrets periodically:
# Generate new key
NEW_KEY=$(openssl rand -hex 32)

# Update secret
sst secret set EncryptionKey $NEW_KEY --stage production

# Deploy to apply
sst deploy --stage production

Limit access

Only link secrets to functions that need them:
// Good - only link what's needed
const publicApi = new sst.aws.Function("PublicApi", {
  handler: "src/public.handler",
  link: [], // No secrets needed
});

const paymentApi = new sst.aws.Function("PaymentApi", {
  handler: "src/payment.handler",
  link: [stripeKey], // Only Stripe key
});

// Avoid - linking everything everywhere
const fn = new sst.aws.Function("Fn", {
  handler: "src/handler.handler",
  link: [stripeKey, twilioKey, sendgridKey], // Too broad?
});

Document required secrets

Document secrets in your README:
README.md
## Required Secrets

Set these secrets before deploying:

```bash
sst secret set StripeKey <your-key>
sst secret set SendgridApiKey <your-key>
sst secret set DatabasePassword <your-password>

### Use fallbacks for non-sensitive config

Fallbacks work well for:
- Third-party API keys (same across stages)
- Feature flags
- Non-sensitive configuration

Avoid fallbacks for:
- Production credentials
- Database passwords
- Encryption keys

## Security considerations

### Never commit secrets

Add these to `.gitignore`:

```gitignore title=".gitignore"
# Secrets
*.env
*.pem
*.key
secrets.*
.sst/secrets

Use CI/CD secrets

In CI/CD, set secrets as environment variables:
.github/workflows/deploy.yml
- name: Deploy
  run: |
    sst secret set StripeKey ${{ secrets.STRIPE_KEY }}
    sst deploy --stage production

Audit secret access

Check which functions have access:
# View function permissions
aws lambda get-policy --function-name my-app-production-MyApi

Monitor secret usage

Set up CloudWatch alarms for:
  • KMS decrypt operations
  • S3 access to secrets bucket
  • Lambda function errors (might indicate secret issues)

Migrating from environment variables

If you’re using environment variables, migrate to secrets:

Before (environment variables)

const fn = new sst.aws.Function("MyFunction", {
  handler: "src/handler.handler",
  environment: {
    STRIPE_KEY: process.env.STRIPE_KEY!, // Not secure
  },
});

After (secrets)

const stripeKey = new sst.Secret("StripeKey");

const fn = new sst.aws.Function("MyFunction", {
  handler: "src/handler.handler",
  link: [stripeKey],
});
Set the value:
sst secret set StripeKey sk_test_abc123
Update your code:
// Before
const stripe = new Stripe(process.env.STRIPE_KEY!);

// After
import { Resource } from "sst";
const stripe = new Stripe(Resource.StripeKey.value);

Troubleshooting

Secret not found

If you get “secret not found” errors:
  1. Check that the secret is set:
    sst secret list
    
  2. Verify the stage matches:
    sst secret list --stage production
    
  3. Set the secret if missing:
    sst secret set SecretName value
    

Secret not updating

After changing a secret:
  1. If using sst dev, it updates automatically
  2. If deployed, run sst deploy to apply changes
  3. Functions must be reinvoked to see new values

Permission errors

If you get KMS permission errors:
  1. Check your IAM permissions include:
    • kms:Decrypt
    • kms:DescribeKey
    • s3:GetObject on secrets bucket
  2. Verify the execution role has access

Next steps

Linking

Learn about resource linking

Deployment

Deploy with secrets

Environment Variables

Use environment variables

Security

Security best practices

Build docs developers (and LLMs) love