Trying to use Caddy as a reverse proxy for a Rust web app

1. The problem I’m having:

I built a web app using the Rust framework Axum and am trying to use Caddy as a reverse proxy, but I’m getting 502 errors on my domain https://wisderm.com/.

The web app is running so why is Caddy unable to establish a connection?

$ docker logs wisderm-app-1 
    Finished release [optimized] target(s) in 1.77s
warning: the following packages contain code that will be rejected by a future version of Rust: nom v5.1.2
note: to see what the problems were, use the option `--future-incompat-report`, or run `cargo report future-incompatibilities --id 1`
     Running `target/release/wisderm`

2. Error messages and/or full log output:

$ docker logs wisderm-caddy-1 
{"level":"info","ts":1682517767.0923154,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
{"level":"warn","ts":1682517767.096441,"msg":"Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
{"level":"info","ts":1682517767.115291,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
{"level":"info","ts":1682517767.1181366,"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}
{"level":"info","ts":1682517767.1181595,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
{"level":"info","ts":1682517767.1253803,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/data/caddy"}
{"level":"info","ts":1682517767.1365693,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0003193b0"}
{"level":"info","ts":1682517767.1368098,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
{"level":"debug","ts":1682517767.1424606,"logger":"http","msg":"starting server loop","address":"[::]:443","tls":true,"http3":true}
{"level":"info","ts":1682517767.1424818,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
{"level":"debug","ts":1682517767.1425407,"logger":"http","msg":"starting server loop","address":"[::]:80","tls":false,"http3":false}
{"level":"info","ts":1682517767.142548,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
{"level":"info","ts":1682517767.1425524,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["wisderm.com"]}
{"level":"debug","ts":1682517767.1448789,"logger":"tls","msg":"loading managed certificate","domain":"wisderm.com","expiration":1690017381,"issuer_key":"acme-v02.api.letsencrypt.org-directory","storage":"FileStorage:/data/caddy"}
{"level":"debug","ts":1682517767.1452851,"logger":"tls.cache","msg":"added certificate to cache","subjects":["wisderm.com"],"expiration":1690017381,"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a","cache_size":1,"cache_capacity":10000}
{"level":"debug","ts":1682517767.1455686,"logger":"events","msg":"event","name":"cached_managed_cert","id":"e58df294-4c0d-44bd-aa87-ff450fed4b49","origin":"tls","data":{"sans":["wisderm.com"]}}
{"level":"info","ts":1682517767.1466587,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
{"level":"info","ts":1682517767.1466744,"msg":"serving initial configuration"}
{"level":"info","ts":1682517767.1484466,"logger":"tls","msg":"finished cleaning storage units"}
{"level":"debug","ts":1682517825.305438,"logger":"events","msg":"event","name":"tls_get_certificate","id":"404df34d-8a99-403d-8420-f4ebd90ed410","origin":"tls","data":{"client_hello":{"CipherSuites":[39578,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"wisderm.com","SupportedCurves":[14906,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[14906,772,771],"Conn":{}}}}
{"level":"debug","ts":1682517825.3071008,"logger":"tls.handshake","msg":"choosing certificate","identifier":"wisderm.com","num_choices":1}
{"level":"debug","ts":1682517825.3073769,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"wisderm.com","subjects":["wisderm.com"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682517825.3076131,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"121.6.14.20","remote_port":"34704","subjects":["wisderm.com"],"managed":true,"expiration":1690017381,"hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682517825.311837,"logger":"events","msg":"event","name":"tls_get_certificate","id":"bed4b58f-2e3b-4220-b277-f30d7312e09d","origin":"tls","data":{"client_hello":{"CipherSuites":[64250,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"wisderm.com","SupportedCurves":[27242,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[23130,772,771],"Conn":{}}}}
{"level":"debug","ts":1682517825.3120992,"logger":"tls.handshake","msg":"choosing certificate","identifier":"wisderm.com","num_choices":1}
{"level":"debug","ts":1682517825.3122845,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"wisderm.com","subjects":["wisderm.com"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682517825.3124912,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"121.6.14.20","remote_port":"34688","subjects":["wisderm.com"],"managed":true,"expiration":1690017381,"hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682517825.5887516,"logger":"events","msg":"event","name":"tls_get_certificate","id":"584967a7-ba3b-4ec8-8314-582a91915142","origin":"tls","data":{"client_hello":{"CipherSuites":[23130,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"wisderm.com","SupportedCurves":[27242,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[47802,772,771],"Conn":{}}}}
{"level":"debug","ts":1682517825.589097,"logger":"tls.handshake","msg":"choosing certificate","identifier":"wisderm.com","num_choices":1}
{"level":"debug","ts":1682517825.5892577,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"wisderm.com","subjects":["wisderm.com"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682517825.58943,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"121.6.14.20","remote_port":"34708","subjects":["wisderm.com"],"managed":true,"expiration":1690017381,"hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682517825.602956,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":":3000","total_upstreams":1}
{"level":"debug","ts":1682517825.603911,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":":3000","duration":0.000620755,"request":{"remote_ip":"121.6.14.20","remote_port":"34704","proto":"HTTP/2.0","method":"GET","host":"wisderm.com","uri":"/","headers":{"Accept-Encoding":["gzip, deflate, br"],"X-Forwarded-Host":["wisderm.com"],"Sec-Fetch-Dest":["document"],"X-Forwarded-For":["121.6.14.20"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh;q=0.6"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\""],"Sec-Fetch-Mode":["navigate"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Fetch-Site":["none"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"X-Forwarded-Proto":["https"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"Cache-Control":["max-age=0"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"wisderm.com"}},"error":"dial tcp :3000: connect: connection refused"}
{"level":"error","ts":1682517825.605828,"logger":"http.log.error","msg":"dial tcp :3000: connect: connection refused","request":{"remote_ip":"121.6.14.20","remote_port":"34704","proto":"HTTP/2.0","method":"GET","host":"wisderm.com","uri":"/","headers":{"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh;q=0.6"],"Sec-Ch-Ua-Mobile":["?0"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Upgrade-Insecure-Requests":["1"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Cache-Control":["max-age=0"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\""],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Site":["none"],"Sec-Fetch-Mode":["navigate"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"wisderm.com"}},"duration":0.003090089,"status":502,"err_id":"jw5m6qdmp","err_trace":"reverseproxy.statusError (reverseproxy.go:1299)"}
{"level":"debug","ts":1682518143.1216724,"logger":"events","msg":"event","name":"tls_get_certificate","id":"7b8cf641-70b4-4666-b690-090d7ff0dfed","origin":"tls","data":{"client_hello":{"CipherSuites":[4865,4866,4867],"ServerName":"wisderm.com","SupportedCurves":[29,23,24],"SupportedPoints":null,"SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537,513],"SupportedProtos":["h3"],"SupportedVersions":[772],"Conn":{}}}}
{"level":"debug","ts":1682518143.1220875,"logger":"tls.handshake","msg":"choosing certificate","identifier":"wisderm.com","num_choices":1}
{"level":"debug","ts":1682518143.1221685,"logger":"tls.handshake","msg":"default certificate selection results","identifier":"wisderm.com","subjects":["wisderm.com"],"managed":true,"issuer_key":"acme-v02.api.letsencrypt.org-directory","hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682518143.1221952,"logger":"tls.handshake","msg":"matched certificate in cache","remote_ip":"121.6.14.20","remote_port":"34870","subjects":["wisderm.com"],"managed":true,"expiration":1690017381,"hash":"2f38ba06585c3b4e70995538625361d3612d802cdf6926c964c37df4bccfe90a"}
{"level":"debug","ts":1682518143.6025012,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":":3000","total_upstreams":1}
{"level":"debug","ts":1682518143.6035635,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":":3000","duration":0.000639808,"request":{"remote_ip":"121.6.14.20","remote_port":"34870","proto":"HTTP/3.0","method":"GET","host":"wisderm.com","uri":"/","headers":{"X-Forwarded-Host":["wisderm.com"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"],"Sec-Fetch-Mode":["navigate"],"Sec-Ch-Ua-Mobile":["?0"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-User":["?1"],"Sec-Ch-Ua-Platform":["\"Linux\""],"X-Forwarded-For":["121.6.14.20"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh;q=0.6"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\""],"X-Forwarded-Proto":["https"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-Site":["none"],"Sec-Fetch-Dest":["document"],"Cache-Control":["max-age=0"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h3","server_name":"wisderm.com"}},"error":"dial tcp :3000: connect: connection refused"}
{"level":"error","ts":1682518143.603932,"logger":"http.log.error","msg":"dial tcp :3000: connect: connection refused","request":{"remote_ip":"121.6.14.20","remote_port":"34870","proto":"HTTP/3.0","method":"GET","host":"wisderm.com","uri":"/","headers":{"Sec-Fetch-Mode":["navigate"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Ch-Ua":["\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\""],"Sec-Ch-Ua-Mobile":["?0"],"User-Agent":["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"],"Sec-Fetch-Site":["none"],"Sec-Fetch-User":["?1"],"Sec-Fetch-Dest":["document"],"Accept-Language":["en-GB,en-US;q=0.9,en;q=0.8,zh-CN;q=0.7,zh;q=0.6"],"Cache-Control":["max-age=0"],"Sec-Ch-Ua-Platform":["\"Linux\""],"Upgrade-Insecure-Requests":["1"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h3","server_name":"wisderm.com"}},"duration":0.001623581,"status":502,"err_id":"drsigmckk","err_trace":"reverseproxy.statusError (reverseproxy.go:1299)"}

3. Caddy version:

$ docker exec -it wisderm-caddy-1 caddy version
v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=

4. How I installed and ran Caddy:

I installed and ran Caddy using docker compose.

a. System environment:

I’m using a Vultr VPS with 1 vCPU, 1 GB RAM and 25 GB SSD storage. The OS is Debian 11 x64 (bullseye).

b. Command:

docker compose -f docker-compose.yml up -d --build
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

c. Service/unit/compose file:

This is my docker-compose.yml.

version: '3.4'
services:

  db:
    image: postgres:15-alpine
    env_file:
      - .env
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    volumes:
      - postgres:/var/lib/postgresql/data

  meilisearch:
    image: getmeili/meilisearch:v1.1
    env_file:
      - .env
    environment:
      MEILI_NO_ANALYTICS: true
      MEILI_ENV: production
    ports:
      - "7700:7700"
    volumes:
      - meili:/meili_data

  app:
    build: 
      context: .
      dockerfile: Dockerfile
      target: development
    volumes:
      - .:/wisderm:cached
      # Give access to ssh keys on the host (Is there a better way to do this?)
      - ~/.ssh:/home/host-ssh:cached
      # We need this so docker in docker works
      - /var/run/docker.sock:/var/run/docker.sock
      - target:/wisderm/target # Set target as a volume for performance. 
      - migration-target:/wisderm/migration/target
      # Uncomment the next line to improve performance when using node.
      - node_modules:/wisderm/node_modules
    env_file:
      - .env
    ports:
      - "3000:3000"
    # Overrides default command so things don't shut down after the process ends.
    command: sleep infinity
    working_dir: /wisderm
    depends_on:
      - db
      - meilisearch

  caddy:
    image: caddy:2.6-alpine
    restart: unless-stopped
    ports:
      # HTTP
      - "80:80"
      # HTTPS
      - "443:443"
      # HTTP/3
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

volumes:
  postgres:
  meili:
  caddy_data:
  caddy_config:
  target:
  migration-target:
  node_modules:

This is my docker-compose.prod.yml.

version: '3.4'
services:

  app:
    build: 
      context: .
      dockerfile: Dockerfile
      target: production
    command: cargo run --release
    restart: always

d. My complete Caddy config:

This is my Caddyfile.

https://wisderm.com:443

file_server /wisderm/static
reverse_proxy :3000

You didn’t specify an IP/hostname, so that means Caddy assumes “localhost”. Inside of Docker, localhost means “this container”, so Caddy is trying to connect to something on port 3000 inside its own container. That won’t work.

You should use the container name of your app instead, so reverse_proxy app:3000

This probably doesn’t do what you expect. This means "serve files for requests to the URL /wisderm/static. I don’t think that was your intent. You need to use root to set the webroot, then you need to use request matchers to tell Caddy which requests should serve static files. I’m not sure what your intent was here, so I can’t suggest anything specific, but if you elaborate on your goal, I could.

Adding the container name of my app did the trick, thanks a lot!

Re: the file server, my static files (images, js etc) are in the /wisderm/static directory, so I changed it to

root * /
file_server /wisderm/static/*

Was this what you meant by using request matchers to tell Caddy which requests should serve static files?

No; you’d want root * /wisderm/static to set the webroot. And using that path matcher for file_server doesn’t make sense, because your requests won’t have that path prefix in them.

Both the proxy and file server directives will “consume” all requests. Due to directive ordering, the proxy will run first, so no requests will reach the file server. You need to use request matchers to tell Caddy “don’t run the proxy if this condition matches”, so it can reach the file server.

You could use the file matcher to ask Caddy to check if the request path matches a file that exists on disk.

root * /wisderm/static
@file-not-exists not file
reverse_proxy @file-not-exists app:3000
file_server

This uses the not matcher to negate the file matcher, so it skips the proxy if the requested file exists on disk. So if /index.js is requested, the file matcher looks to see if /wisderm/static/index.js exists.

Thanks for the detailed example, I see what you’re saying now. If I want to proxy all requests instead, it can be just the following, right?

https://wisderm.com:443 {
    reverse_proxy app:3000
}

Yep.

Although it’s redundant to include https:// and :443 in your site address, those are the defaults.

I’m still having trouble with CORS.

My webapp makes requests to my subdomain meilisearch.wisderm.com for searches.

const searchInstance = function(hitsContainer) {
  return instantsearch({
    indexName: "documents",
    searchClient: instantMeiliSearch(
      "https://meilisearch.wisderm.com",
      process.env.MEILI_SEARCH_API_KEY,
      { placeholderSearch: false },
    ),
    searchFunction(helper) {
      helper.search();
    },
  });
};

I modified my Caddyfile to try to set the Access-Control-Allow-Origin header in the response.

wisderm.com {
    reverse_proxy app:3000
}

meilisearch.wisderm.com {
    reverse_proxy meilisearch:7700 {
        header_down Access-Control-Allow-Origin "https://wisderm.com"
    }
}

But in Chrome’s dev console, when I visit wisderm.com, I’m still getting a CORS error.

wisderm.com/:1
Access to fetch at 'https://meilisearch.wisderm.com/multi-search' from origin 'https://wisderm.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Modifying the Caddyfile as such doesn’t work either, still getting the same CORS error.

(cors) {
  @cors_preflight method OPTIONS
  @cors header Origin {args.0}

  handle @cors_preflight {
    header Access-Control-Allow-Origin "{args.0}"
    header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE"
    header Access-Control-Allow-Headers "Authorization, Content-Type, X-Meilisearch-Client"    
    header Access-Control-Max-Age "3600"
    respond "" 204
  }

  handle @cors {
    header Access-Control-Allow-Origin "{args.0}"
    header Access-Control-Expose-Headers "Link"
  }
}

wisderm.com {
  reverse_proxy app:3000
}

meilisearch.wisderm.com {
  import cors https://wisderm.com
  reverse_proxy meilisearch:7700
}

What does it look like when you make a request with curl -v? What response headers do you see?

Ultimately, CORS issues aren’t really a Caddy issue, they’re a browser/app issue.

This is the output of curl -v.

$ curl -v meilsearch.wisderm.com/multi-search
*   Trying 45.77.220.125:80...
* Connected to meilsearch.wisderm.com (45.77.220.125) port 80 (#0)
> GET /multi-search HTTP/1.1
> Host: meilsearch.wisderm.com
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://meilsearch.wisderm.com/multi-search
< Server: Caddy
< Date: Sun, 30 Apr 2023 08:41:22 GMT
< Content-Length: 0
< 
* Closing connection 0

My Axum webapp had a CORS layer previously, but I removed it. Now the preflight requests are succeeding, and I’m getting a different error for the actual requests.

Access to fetch at 'https://meilisearch.wisderm.com/multi-search' from origin 'https://wisderm.com' has been blocked by CORS policy:
The 'Access-Control-Allow-Origin' header contains multiple values 'https://wisderm.com, *', but only one is allowed.
Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Turns out Meilisearch already handles CORS for the actual requests, so I only needed Caddy to handle the preflight ones.

1 Like

You made an HTTP request there, so all you’re seeing is the HTTP->HTTPS redirect response. Make sure to use https:// in the curl command.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.