Serverless computing removes servers from your to-do list, but not security. These serverless security best practices cover what changes when you move to functions, what stays your responsibility, and the specific controls that protect a serverless application in production. Examples use AWS Lambda, and the same ideas apply to Azure Functions and Google Cloud Run functions.
What Is Serverless Security?
Serverless security is protecting the parts of a serverless application the cloud provider doesn’t: your code, its permissions, its data, and the events that trigger it. Under the shared responsibility model, the split looks like this:
| The provider secures | You secure |
|---|---|
| Physical hosts, hypervisor and operating system | Function code and its dependencies |
| Runtime patching (for managed runtimes) | IAM permissions for each function |
| Isolation between customers’ functions | Input validation for every event source |
| Availability and scaling of the platform | Secrets, data encryption choices, and API authentication |
How Serverless Improves Security, and What It Adds
Serverless genuinely removes some risks: there’s no operating system for you to patch, no long-running server for an attacker to settle into, and each function can have its own narrowly scoped permissions. But it adds others. An application becomes dozens of small functions, each with its own permissions and triggers, and every trigger (an HTTP request, a file upload, a queue message) is an input an attacker may control. The OWASP Serverless Top 10 lists event-data injection, broken authentication and over-privileged functions among the most common problems.
1. Give Every Function Its Own Least-Privilege Role
The most important practice is also the most often skipped. One shared role with broad permissions means one vulnerable function exposes everything. Scope each role to specific actions and resources:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
"Resource": "arn:aws:dynamodb:us-west-2:123456789012:table/orders"
}]
}Avoid "Action": "*" and "Resource": "*" in function roles. IAM Access Analyzer can generate a policy from a function’s actual CloudTrail activity, which is a fast way to tighten an existing role.
2. Validate Input from Every Event Source
Functions are triggered by far more than HTTP. An S3 object key, an SQS message body or a DynamoDB stream record can all carry attacker-controlled data. Validate every event against a schema before using it, never pass event fields directly into shell commands, SQL or file paths, and treat internal queues as untrusted if anything upstream accepts public input.
3. Keep Secrets Out of Code and Plain Environment Variables
- Store credentials in AWS Secrets Manager or Systems Manager Parameter Store (SecureString), encrypted with KMS, and fetch them at runtime. Cache them for the life of the execution environment to keep latency and cost down.
- If you must use environment variables, encrypt them with a customer-managed KMS key and limit who can read the function configuration.
- Never commit secrets to the repository or bake them into deployment packages or layers.
4. Authenticate and Throttle Every Entry Point
- Put API Gateway in front of public functions with a JWT or Cognito authorizer, or IAM authorization for service-to-service calls.
- If you use Lambda function URLs, prefer
AuthType: AWS_IAM.NONEmakes the function publicly callable by anyone with the URL. - Set throttling limits on API stages and attach AWS WAF rules for common attacks and rate-based blocking.
5. Limit Timeouts and Concurrency to Prevent “Denial of Wallet”
Serverless scales automatically, and so does the bill when someone floods an endpoint. Set function timeouts close to real execution time rather than the maximum, use reserved concurrency to cap how far a single function can scale, and set billing alarms. This protects both your budget and the downstream databases that can’t scale as fast as Lambda.
6. Secure Dependencies and Deployments
- Scan dependencies in CI (npm audit, pip-audit, or a scanner like Trivy) and fail builds on high-severity issues.
- Keep functions on supported runtimes. Deprecated runtimes stop receiving security patches.
- Use code signing for Lambda (with AWS Signer) so only packages your pipeline signed can be deployed.
- Deploy through infrastructure as code (Terraform, AWS SAM or CDK) so permissions are reviewed in pull requests, not clicked together in the console.
7. Log, Trace and Detect
- Enable CloudTrail for management events, and data events for sensitive functions and buckets.
- Send structured logs to CloudWatch Logs with a sensible retention period, and never log secrets or full request payloads with personal data.
- Use X-Ray or OpenTelemetry tracing to see how requests flow across functions.
- Turn on GuardDuty Lambda Protection to flag functions communicating with known malicious hosts.
8. Use Network Controls Only Where They Help
Functions don’t need to be in a VPC unless they reach private resources such as an RDS database. When they do, use private subnets, security groups scoped to exactly the ports needed, and VPC endpoints for AWS services, so traffic to S3 or DynamoDB doesn’t leave the AWS network.
The Same Controls on Azure and Google Cloud
The practices above aren’t AWS-specific. Here’s where each one lives on the other two major platforms:
| Control | AWS Lambda | Azure Functions | Google Cloud Run functions |
|---|---|---|---|
| Per-function identity | IAM execution role | Managed identity | Dedicated service account |
| Secrets | Secrets Manager, Parameter Store | Key Vault references | Secret Manager |
| Public entry point auth | API Gateway authorizers, IAM function URLs | App Service authentication, API Management | IAM invoker role, API Gateway |
| Scaling caps | Reserved concurrency | Maximum scale-out setting | Maximum instances |
| Threat detection | GuardDuty Lambda Protection | Microsoft Defender for Cloud | Security Command Center |
The most common mistake on every platform is the same: reusing one broad identity across many functions. On Google Cloud in particular, functions fall back to a default service account unless you assign one, and that account often has far more access than any single function needs.
Serverless Security Checklist
| Check | Done when |
|---|---|
| Per-function IAM roles | No wildcard actions or resources in function policies |
| Input validation | Every trigger’s event is schema-validated |
| Secrets | All credentials come from Secrets Manager or Parameter Store |
| Entry points | Every public API or function URL requires authentication and is throttled |
| Cost guardrails | Timeouts, reserved concurrency and billing alarms are set |
| Supply chain | Dependency scanning in CI, supported runtimes, code signing |
| Visibility | CloudTrail, structured logs, tracing and GuardDuty enabled |
The AWS Lambda security documentation goes deeper on each control. If your stack also runs containers, our Kubernetes security best practices guide covers the equivalent controls for clusters. And if you’d like help reviewing a serverless application’s permissions and entry points, our cloud and DevSecOps services start with exactly that review.