Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Enabling HTTPS (SSL) for Laravel Sail using Caddy
<?php
# app/Http/Controllers/CaddyController.php
namespace App\Http\Controllers;
use App\Store;
use Illuminate\Http\Request;
class CaddyController extends Controller
{
public function check(Request $request)
{
$authorizedDomains = [
'laravel.test',
'www.laravel.test',
// Add subdomains here
];
if (in_array($request->query('domain'), $authorizedDomains)) {
return response('Domain Authorized');
}
// Abort if there's no 200 response returned above
abort(503);
}
}
# docker/Caddyfile
{
on_demand_tls {
ask http://laravel.test/caddy-check
}
local_certs
}
:443 {
tls internal {
on_demand
}
reverse_proxy laravel.test {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Port {server_port}
header_up X-Forwarded-Proto {scheme}
health_timeout 5s
}
}
services:
caddy:
image: caddy:latest
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- './docker/Caddyfile:/etc/caddy/Caddyfile'
- sailcaddy:/data
- sailcaddy:/config
networks:
- sail
# Remove "ports" from laravel.test service
volumes:
sailcaddy:
driver: local
<?php
# routes/web.php
Route::get('/caddy-check', 'CaddyController@check');
@njoguamos
Copy link

njoguamos commented Sep 24, 2021

Thanks

@techops01
Copy link

techops01 commented Oct 7, 2021

Bullshit. Not working

docker-compose up -d
ERROR: Service "caddy" uses an undefined network "sail"

@gilbitron
Copy link
Author

gilbitron commented Oct 8, 2021

ERROR: Service "caddy" uses an undefined network "sail"

This is not the full docker-compose.yml file, just the changes you need to make to the one generated by sail.

@domsii
Copy link

domsii commented Oct 20, 2021

Thanks ! But I ran in a Problem ...
The Caddyfile is a file in the "docker" subdirectory of the laravel project - correct ?
Or has the Caddyfile to be in a directory "Caddyfile" ?

I get an error :
ERROR: for test-project_caddy_1 Cannot start service caddy: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: rootfs_linux.go:76: mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu/30bced14b9bc1133143e6086f29c9ed8d5aa55c5aa4d4cd84a64037bbd9f28ef" to rootfs at "/etc/caddy/Caddyfile" caused: mount through procfd: not a directory: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type

ERROR: for caddy Cannot start service caddy: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: rootfs_linux.go:76: mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu/30bced14b9bc1133143e6086f29c9ed8d5aa55c5aa4d4cd84a64037bbd9f28ef" to rootfs at "/etc/caddy/Caddyfile" caused: mount through procfd: not a directory: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type
ERROR: Encountered errors while bringing up the project.

@gilbitron
Copy link
Author

gilbitron commented Oct 20, 2021

The Caddyfile should be in a ./docker subdirectory yes. This line is how the file is mounted to the container:

volumes:
      - './docker/Caddyfile:/etc/caddy/Caddyfile'

@kimcee
Copy link

kimcee commented Oct 22, 2021

This all seems to work until the last two steps. Going to https://localdev.test doesn't work, chrome says "This site can’t provide a secure connection localdev.test sent an invalid response. ERR_SSL_PROTOCOL_ERROR". Any ideas?

UPDATE: Was able to get the root.crt into Keychain and set it to Always Trust. Still getting the same error when trying to access the site in chrome, not sure what I'm missing.

@CarterBland
Copy link

CarterBland commented Nov 16, 2021

@kimcee ensure that you're pointing to port 443, I had the same issue @ https://localhost:80 but switching to https://localhost:443 resolved it.

@dvlpr91
Copy link

dvlpr91 commented Nov 22, 2021

This all seems to work until the last two steps. Going to https://localdev.test doesn't work, chrome says "This site can’t provide a secure connection localdev.test sent an invalid response. ERR_SSL_PROTOCOL_ERROR". Any ideas?

UPDATE: Was able to get the root.crt into Keychain and set it to Always Trust. Still getting the same error when trying to access the site in chrome, not sure what I'm missing.

So am I.

Have you changed the values ​​of 'laravel.test' or 'www.laravel.test' in 'authorizedDomains' and 'Caddyfile' to the values ​​you want? (ex: 'https://localdev.test')

@dvlpr91
Copy link

dvlpr91 commented Nov 22, 2021

// CaddyController.php

...
$authorizedDomains = [
  'foo.bar.test',
];
...
// Caddyfile

...
reverse_proxy foo.bar.test {
...

but.. not working...
I see the message 'ERR_SSL_PROTOCOL_ERROR' in Chrome.

@gilbitron
Copy link
Author

gilbitron commented Nov 22, 2021

If you're seeing ERR_SSL_PROTOCOL_ERROR it usually means Caddy hasn't been able to generate a certificate for the domain. A good place to start is checking the Caddy logs:

sail logs -f caddy

This usually happens when something either isn't right with your Docker config or something has broken the app itself. For example, Caddy's request to the CaddyController has returned a 500 error. In this case, you can check your Laravel logs to see what's going. Some examples that I've run into include composer install needing to be run, php artisan migrate needing to be run, etc.

@dvlpr91
Copy link

dvlpr91 commented Nov 24, 2021

thank you. I was able to solve it well.

However, I have a question.

If there are multiple projects, docker volume will be created for each project(ex: foo_sailcaddy, bar_sailcaddy), and multiple caddy root certificates will need to be registered.

Are there any good alternatives to work around this?

@clytras
Copy link

clytras commented Dec 5, 2021

Hello and thank you for the great solution. I upvoted and commented on stackoverflow asking about HMR.

I want to say that I got all working after 16 hours with lots of trial/error/debugging/coffee and wine!

The solution works great for local development using Laravel Sail with different service names, totally different ports and even Laravel Mix having Webpack DevServer loading React Fast Refresh through HRM module on different ports.

I've been tricked by the CaddyController as it executes only once for each domain/subdomain and then it caches the result, so I thought it was not running, but there is no problem with it.

Then I've swapped ports from app service (in my case "someapp.test") to caddy container, change domain names, fetched/convert and install the root.crt/root.key > root.p12 and the app got load with HTTPS and with no errors.

For the Laravel Mix Webpack Server HMR and anyone that wants to have it, I'm having a setup like this...

Inside a Mix config file set the hmrOptions and devServer options:

mix.options({
  hmrOptions: {
      host: 'localhost',
      port: process.env.DEV_HOT_PORT,
  },
});

mix.webpackConfig({
  output: {
    chunkFilename: `[name].[chunkhash].js`,
  },
  devServer: {
      host: '0.0.0.0',
      port: process.env.DEV_HOT_PORT,
  },
});

and I'm using @pmmmwh/react-refresh-webpack-plugin for binding React Refresh with the Webpack dev-server and with .env settings:

APP_DOMAIN="someapp.test"
DEV_DOMAIN="dev.someapp.test"
DEV_HOT_PORT=12181
DEV_URL="//${DEV_DOMAIN}"

someapp service inside docker-compose.yml:

someapp.test:
  build:
    context: ./docker/8.0
    dockerfile: Dockerfile
    args:
      WWWGROUP: '${WWWGROUP}'
  image: sail-8.0/app
  ports:
    - '${DEV_HOT_PORT:-8080}:${DEV_HOT_PORT:-8080}'
  environment:
    WWWUSER: '${WWWUSER}'
    LARAVEL_SAIL: 1
    LANG: 'C.UTF-8'
    TERM: 'xterm-256color'
  volumes:
    - '.:/var/www/html'
    - '${LIBRARY_PATH:?LIBRARY_PATH__NOTSET}:/var/www/lib'
  networks:
    - someappnet
  depends_on:
    - mariadb
    - mongodb
    - redis
    - meilisearch

caddy:
  image: caddy:latest
  restart: unless-stopped
  ports:
    - '${APP_PORT:-80}:80'
    - '${APP_SECURE_PORT:-443}:443'
  environment:
    LARAVEL_SAIL: 1
    HOST_DOMAIN: ${APP_DOMAIN}
    APP_SECURE_PORT: '${APP_SECURE_PORT}'
    DEV_HOT_PORT: '${DEV_HOT_PORT}'
  volumes:
    - './docker/caddy/Caddyfile:/etc/caddy/Caddyfile'
    - './docker/caddy/certificates:/data/caddy/certificates/local'
    - './docker/caddy/authorities:/data/caddy/pki/authorities/local'
    - 'caddy:/data:cached'
    - 'caddy:/config:cached'
  networks:
    - someappnet
  depends_on:
    - someapp.test

and inside config/app.php:

// \Illuminate\Foundation\Mix.php#L35
'mix_hot_proxy_url' => env('DEV_URL', null),

and loading the assets using just mix Blade directive which is a binding of Illuminate\Foundation\Mix.php class:

<script src="{{ mix('/js/platform.js') }}" defer></script>

all the scripts will have a URL like https://dev.someapp.test/js/platform.js and these will be parsed by Webpack dev-server HMR module.

The HMR port has to stay on the service container "someapp.test", because there is where the Webpack dev-server is running and we just need to proxy pass our dev.someapp.test domain there.

And then inside the Caddyfile, which we have replace :423 with https:// and inside define macthers for each service subdomain and reverse proxy them to their internal ports:

{
  # debug

  on_demand_tls {
    ask http://{$HOST_DOMAIN}/~~local/caddy-check
  }

  local_certs
}

https:// {
  tls internal {
    on_demand
  }

  # Webpack HMR

  @devhmr {
    host dev.{$HOST_DOMAIN}
  }

  reverse_proxy @devhmr {$HOST_DOMAIN}:{$DEV_HOT_PORT} {
    # https://caddy.community/t/context-cancelled-when-webpack-hmr-sends-updates-fix/9850
    flush_interval -1
  }

  # PMA

  @pma {
    host pma.{$HOST_DOMAIN}
  }

  reverse_proxy @pma phpmyadmin:80

  # Mongo Express

  @mongo {
    host mongoexpress.{$HOST_DOMAIN}
  }

  reverse_proxy @mongo mongoexpress:8081

  # Mailhog

  @mailhog {
    host mailhog.{$HOST_DOMAIN}
  }

  reverse_proxy @mailhog mailhog:8025

  # Meilisearch

  @meilisearch {
    host meilisearch.{$HOST_DOMAIN}
  }

  reverse_proxy @meilisearch meilisearch:7700

  # Proxy everything else to Laravel someapp.test service
  reverse_proxy {$HOST_DOMAIN}
}

Everything is working now localy on "someapp.test" with valid SSL and React Fast Refresh working through Laravel Mix.

Thanks again!

@hubtraum
Copy link

hubtraum commented Jan 14, 2022

In case someone is falling over the same thing, I needed to add the namespace for the route in web.php:

Route::get('/caddy-check', 'App\Http\Controllers\CaddyController@check');

Because I did not have any other web routes or namespace usage in the file.

@anselmobattisti
Copy link

anselmobattisti commented Feb 11, 2022

Thanks for your help.

To enable a real SSL certified you should:

#
# Config that should be executed in the server
#

{
	email your_valid@email.com
}

your_domail.com {
 
    reverse_proxy laravel.test {
       header_up Host {host}
       header_up X-Real-IP {remote}
       header_up X-Forwarded-For {remote}
       header_up X-Forwarded-Port {server_port}
       header_up X-Forwarded-Proto {scheme}
       health_timeout 5s
   }
}

I got stuck to understand that in the "reverse_proxy" you MUST define the name of the service you want to reverse proxy. It is not the real domain!

@WebKudu
Copy link

WebKudu commented Mar 5, 2022

I'm also getting ERR_SSL_PROTOCOL_ERROR (Chrome) or SSL_ERROR_INTERNAL_ERROR_ALERT (Firefox).

It would be nice to know what @dvlpr91 did to solve the issue.

Looking through the caddy logs, the only thing that stands out is:
Warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again.
Neither apt nor yum are in the caddy container.

The /caddy-check end point seems to be working. I copied in all 4 files with little to no changes.

I didn't attempt to trust the keys yet since it doesn't seem to be getting far enough anyway.

Any ideas?

@alexanderp99
Copy link

alexanderp99 commented Mar 17, 2022

Same problem here. I don't know how to proceed.

I installed a fresh sail project

curl -s "https://laravel.build/example-app" | bash 

Add the files, removed the ports of the laravel application, added the namespace in web.php . Changed the Caddy Image Version to caddy:2.4.2 .

Ran

sail composer install && sail composer update && sail artisan migrate

Logs of caddy are:

example-app-caddy-1  | {"level":"info","ts":1647520369.6394668,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
example-app-caddy-1  | 2022/03/17 12:32:49 [WARNING] Unnecessary header_up ('X-Forwarded-Proto' field): the reverse proxy's default behavior is to pass headers to the upstream
example-app-caddy-1  | {"level":"warn","ts":1647520369.6403213,"msg":"input is not formatted with 'caddy fmt'","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
example-app-caddy-1  | {"level":"info","ts":1647520369.641571,"logger":"admin","msg":"admin endpoint started","address":"tcp/localhost:2019","enforce_origin":false,"origins":["localhost:2019","[::1]:2019","127.0.0.1:2019"]}       
example-app-caddy-1  | {"level":"info","ts":1647520369.6419103,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0000de0e0"}
example-app-caddy-1  | {"level":"info","ts":1647520369.6480498,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443} 
example-app-caddy-1  | {"level":"info","ts":1647520369.6480796,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
example-app-caddy-1  | {"level":"warn","ts":1647520369.6753442,"logger":"pki.ca.local","msg":"installing root certificate (you might be prompted for password)","path":"storage:pki/authorities/local/root.crt"}
example-app-caddy-1  | 2022/03/17 12:32:49 Warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again
example-app-caddy-1  | 2022/03/17 12:32:49 define JAVA_HOME environment variable to use the Java trust
example-app-caddy-1  | 2022/03/17 12:32:49 certificate installed properly in linux trusts
example-app-caddy-1  | {"level":"info","ts":1647520369.699206,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/data/caddy"}
example-app-caddy-1  | {"level":"info","ts":1647520369.6992402,"logger":"tls","msg":"finished cleaning storage units"}
example-app-caddy-1  | {"level":"info","ts":1647520369.699266,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
example-app-caddy-1  | {"level":"info","ts":1647520369.6992774,"msg":"serving initial configuration"}

@szhorvath
Copy link

szhorvath commented May 30, 2022

```shell
DEV_URL="//${DEV_DOMAIN}"

Hi @clytras ,
I've been trying to set up the HMR based on your comment.
The https://dev.someapp.test/js/platform.js return 502 for me.
Do you have any idea what I am doing wrong?

Many thanks

@VRGunnar
Copy link

VRGunnar commented Jun 10, 2022

ERROR: for fible_caddy_1 Cannot start service caddy: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: rootfs_linux.go:76: mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu/91c064ef9216261a3f192079b2607b6ab73173626592a27b5f976cffbb10215e" to rootfs at "/etc/caddy/Caddyfile" caused: mount through procfd: not a directory: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type

ERROR: for caddy Cannot start service caddy: OCI runtime create failed: container_linux.go:380: starting container process caused: process_linux.go:545: container init caused: rootfs_linux.go:76: mounting "/run/desktop/mnt/host/wsl/docker-desktop-bind-mounts/Ubuntu/91c064ef9216261a3f192079b2607b6ab73173626592a27b5f976cffbb10215e" to rootfs at "/etc/caddy/Caddyfile" caused: mount through procfd: not a directory: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type
ERROR: Encountered errors while bringing up the project.

I run into this problem shown above. I saw in some previous comment that the Caddyfile is supposed to be placed in the docker directory in the root of the project. So I did that, but still having the same exact error. Now my Caddyfile has the content shown above as in the Github file in the directory ProjectName/docker/Caddyfile.
image_2022-06-10_182318091
Am I missing something else?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment