Serverless Laravel applications

This guide helps you run Laravel applications on AWS Lambda using Bref. These instructions are kept up to date to target the latest Laravel version.

A demo application is available on GitHub at


First, make sure you have followed the Installation guide to create an AWS account and install the necessary tools.

Next, in an existing Laravel project, install Bref and the Laravel-Bref package.

composer require bref/bref bref/laravel-bridge --update-with-dependencies

Then let's create a serverless.yml configuration file:

php artisan vendor:publish --tag=serverless-config

How it works

By default, the Laravel-Bref package will automatically configure Laravel to work on AWS Lambda

If you are curious, the package will:

  • enable the stderr log driver, to send logs to CloudWatch (read more about logs)
  • enable the cookie session driver
    • if you don't need sessions (e.g. for an API), you can manually set SESSION_DRIVER=array in .env
    • if you prefer, you can configure sessions to be store in database or Redis
  • move the cache directory to /tmp (because the default storage directory is read-only on Lambda)
  • adjust a few more settings (have a look at the BrefServiceProvider for details)


We do not want to deploy caches that were generated on our machine (because paths will be different on AWS Lambda). Let's clear them before deploying:

php artisan config:clear

Let's deploy now:

serverless deploy

When finished, the deploy command will show the URL of the application.

Deploying for production

At the moment, we deployed our local installation to Lambda. When deploying for production, we probably don't want to deploy:

  • development dependencies,
  • our local .env file,
  • or any other dev artifact.

Follow the deployment guide for more details.


In case your application is showing a blank page after being deployed, have a look at the logs.

Trusted proxies

Because Laravel is executed through API Gateway, the Host header is set to the API Gateway host name. Helper functions such as redirect() will use this incorrect domain name. The correct domain name is set on the X-Forwarded-Host header.

To get Laravel to use X-Forwarded-Host instead, edit the App\Http\Middleware\TrustProxies middleware and set $proxies to the * wildcard:

class TrustProxies extends Middleware
    // ...
    protected $proxies = '*';

Read more in the official Laravel documentation.


By default, the Bref bridge will move Laravel's cache directory to /tmp to avoid issues with the default cache directory that is read-only.

The /tmp directory isn't shared across Lambda instances: while this works, this isn't the ideal solution for production workloads. If you plan on actively using the cache, or anything that uses it (like API rate limiting), you should instead use Redis or DynamoDB.

Using DynamoDB

To use DynamoDB as a cache store, change this configuration in config/cache.php

  # config/cache.php
  'dynamodb' => [
      'driver' => 'dynamodb',
      'key' => env('AWS_ACCESS_KEY_ID'),
      'secret' => env('AWS_SECRET_ACCESS_KEY'),
      'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
      'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
      'endpoint' => env('DYNAMODB_ENDPOINT'),
+     'attributes' => [
+         'key' => 'id',
+         'expiration' => 'ttl',
+     ]  

Then follow this section of the documentation to deploy your DynamoDB table using the Serverless Framework.

Laravel Artisan

As you may have noticed, we define a function named "artisan" in serverless.yml. That function is using the Console runtime, which lets us run Laravel Artisan on AWS Lambda.

For example, to execute an artisan command on Lambda for the above configuration, run the below command.

vendor/bin/bref cli bref-demo-laravel-artisan <bref options> -- <your command, your options>

For more details follow the "Console" guide.


To deploy Laravel websites, assets need to be served from AWS S3. The easiest approach is to use the Server-side website construct of the Lift plugin.

This will deploy a Cloudfront distribution that will act as a proxy: it will serve static files directly from S3 and will forward everything else to Lambda. This is very close to how traditional web servers like Apache or Nginx work, which means your application doesn't need to change! For more details, see the official documentation.

First install the plugin

serverless plugin install -n serverless-lift

Then add this configuration to your serverless.yml file.

service: laravel


  - ./vendor/bref/bref
  - serverless-lift


    type: server-side-website
      '/js/*': public/js
      '/css/*': public/css
      '/favicon.ico': public/favicon.ico
      '/robots.txt': public/robots.txt
      # add here any file or directory that needs to be served from S3
    # Laravel uses some headers that are not in CloudFront's default whitelist.
    # To add any, we need to list all accepted headers to pass through.
      - Accept
      - Accept-Language
      - Content-Type
      - Origin
      - Referer
      - User-Agent
      - X-Forwarded-Host
      - X-Requested-With
      # Laravel Framework Headers
      - X-Csrf-Token
      # Other Headers (e.g. Livewire, Laravel Nova), uncomment based on your needs
      # - X-Livewire
      # - X-Inertia

Note: the limit of forwardedHeaders for AWS is set to 10

Before deploying, compile your assets using Laravel Mix.

npm run prod

Now deploy your website using serverless deploy. Lift will create all required resources and take care of uploading your assets to S3 automatically.

For more details, see the Websites section of this documentation and the official Lift documentation.

Assets in templates

Assets referenced in templates should be via the asset() helper:

<script src="{{ asset('js/app.js') }}"></script>

If your templates reference some assets via direct path, you should edit them to use the asset() helper:

- <img src="/images/logo.png">
+ <img src="{{ asset('images/logo.png') }}">

File storage on S3

Laravel has a filesystem abstraction that lets us easily change where files are stored. When running on Lambda, you will need to use the s3 adapter to store files on AWS S3. To do this, configure you production .env file:

# .env

Next, we need to create our bucket via serverless.yml:


        # environment variable for Laravel
        AWS_BUCKET: !Ref Storage
                # Allow Lambda to read and write files in the S3 buckets
                -   Effect: Allow
                    Action: s3:*
                        - !Sub '${Storage.Arn}' # the storage bucket
                        - !Sub '${Storage.Arn}/*' # and everything inside

            Type: AWS::S3::Bucket

Because of a misconfiguration shipped in Laravel, the S3 authentication will not work out of the box. You will need to add this line in config/filesystems.php:

        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
+           'token' => env('AWS_SESSION_TOKEN'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),

That's it! The 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY' and 'AWS_SESSION_TOKEN' variables are defined automatically on AWS Lambda, you don't have to define them.

Public files

Laravel has a special disk called public: this disk stores files that we want to make public, like uploaded photos, generated PDF files, etc.

Again, those files cannot be stored on Lambda, i.e. they cannot be stored in the default storage/app/public directory. You need to store those files on S3.

Do not run php artisan storage:link on AWS Lambda: it is now useless, and it will fail because the filesystem is read-only on Lambda.

To store public files on S3, you could simply replace the disk in the code:

- Storage::disk('public')->put('avatars/1', $fileContents);
+ Storage::disk('s3')->put('avatars/1', $fileContents);

but doing this will not let your application work locally. A better solution, but more complex, involves making the public disk configurable. Let's change the configuration in config/filesystems.php:

    | Default Public Filesystem Disk

+   'public' => env('FILESYSTEM_DISK', 'public_local'),


    'disks' => [

        'local' => [
            'driver' => 'local',
            'root' => storage_path('app'),

-        'public' => [
+        'public_local' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',

        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'token' => env('AWS_SESSION_TOKEN'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),

+        's3_public' => [
+            'driver' => 's3',
+            'key' => env('AWS_ACCESS_KEY_ID'),
+            'secret' => env('AWS_SECRET_ACCESS_KEY'),
+            'token' => env('AWS_SESSION_TOKEN'),
+            'region' => env('AWS_DEFAULT_REGION'),
+            'bucket' => env('AWS_PUBLIC_BUCKET'),
+            'url' => env('AWS_URL'),
+        ],


You can now configure the public disk to use S3 by changing your production .env:


Laravel Queues

It is possible to run Laravel Queues on AWS Lambda using Amazon SQS.

A dedicated Bref package is available for this: bref/laravel-bridge.

Laravel Passport

Laravel Passport has a passport:install command. However, this command cannot be run in Lambda because it needs to write files to the storage/ directory.

Instead, here is what you need to do:

  • Run php artisan passport:keys locally to generate key files.

    This command will generate the storage/oauth-private.key and storage/oauth-public.key files, which need to be deployed.

    Depending on how you deploy your application (from your machine, or from CI), you may want to whitelist them in serverless.yml:

              - ...
              # Exclude the 'storage' directory
              - '!storage/**'
              # Except the public and private keys required by Laravel Passport
              - 'storage/oauth-private.key'
              - 'storage/oauth-public.key'
  • You can now deploy the application:

    serverless deploy
  • Finally, you can create the tokens (which is the second part of the passport:install command):

    vendor/bin/bref cli <artisan-function-name> -- passport:client --personal --name 'Laravel Personal Access Client'
    vendor/bin/bref cli <artisan-function-name> -- passport:client --password --name 'Laravel Personal Access Client'

All these steps were replacements of running the passport:install command from the Passport documentation.