Running Caddy server for Next.js app returns "Too Many Redirects"

1. The problem I’m having:


I work for my university and am trying to migrate from NGINX to Caddy to leverage Caddy’s default HTTP/3 support, so any help is very appreciated!


I am trying to run a Caddy server for my Next.js app which is running inside of a Docker container, a container which is running on my EC2 instance on AWS.

First, since my Docker image runs on 0.0.0.0:3000, and I start my container by running:

sudo docker run -d -it -p 3000:3000 <CONTAINER_ID>

Then, I start the Caddy server by running:

sudo caddy run --config /etc/caddy/Caddyfile

2. Error messages and/or full log output:

After starting the Caddy server, I navigate to my domain on Chrome and I get the following error:

This page isn't working

mydomain.com redirected you too many times.
Try deleting your cookies.
ERR_TOO_MANY_REDIRECTS

and in Chrome’s console logs, I can see 23 requests being made, with 20 of those requests being GET requests to my domain.

Here are the logs I get from my Caddy server after enabling debug mode:

2024/06/07 14:38:48.593 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
2024/06/07 14:38:48.595 INFO    adapted config to JSON  {"adapter": "caddyfile"}
2024/06/07 14:38:48.597 INFO    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2024/06/07 14:38:48.597 INFO    http.auto_https 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}
2024/06/07 14:38:48.597 INFO    http.auto_https enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2024/06/07 14:38:48.597 WARN    http.auto_https server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "srv1", "http_port": 80}
2024/06/07 14:38:48.597 DEBUG   http.auto_https adjusted config {"tls": {"automation":{"policies":[{}]}}, "http": {"servers":{"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"headers","response":{"set":{"Access-Control-Allow-Origin":["*"],"Strict-Transport-Security":["max-age=31536000;"]}}},{"handler":"static_response","headers":{"Location":["https://schuage.com{http.request.uri}"]},"status_code":302},{"encodings":{"gzip":{}},"handler":"encode","prefer":["gzip"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}},"srv1":{"listen":[":80"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"headers","response":{"set":{"Access-Control-Allow-Origin":["*"],"Strict-Transport-Security":["max-age=31536000;"]}}},{"encodings":{"gzip":{}},"handler":"encode","prefer":["gzip"]},{"handler":"reverse_proxy","upstreams":[{"dial":"localhost:3000"}]}]}]}],"terminal":true},{},{}],"automatic_https":{"disable":true}}}}}
2024/06/07 14:38:48.598 INFO    http    enabling HTTP/3 listener        {"addr": ":443"}
2024/06/07 14:38:48.599 DEBUG   http    starting server loop    {"address": "[::]:443", "tls": true, "http3": true}
2024/06/07 14:38:48.599 INFO    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2024/06/07 14:38:48.599 DEBUG   http    starting server loop    {"address": "[::]:80", "tls": false, "http3": false}
2024/06/07 14:38:48.599 INFO    http.log        server running  {"name": "srv1", "protocols": ["h1", "h2", "h3"]}
2024/06/07 14:38:48.599 INFO    http    enabling automatic TLS certificate management   {"domains": ["schuage.com"]}
2024/06/07 14:38:48.600 DEBUG   tls.cache       added certificate to cache      {"subjects": ["schuage.com"], "expiration": "2024/09/04 20:33:23.000", "managed": true, "issuer_key": "acme-v02.api.letsencrypt.org-directory", "hash": "baf143dc0e2cfb49985889cc289ebac712f45c1c12311c0642db3662c60bdc6e", "cache_size": 1, "cache_capacity": 10000}
2024/06/07 14:38:48.600 DEBUG   events  event   {"name": "cached_managed_cert", "id": "011504f0-d195-46d9-ad0b-69d5255e73ec", "origin": "tls", "data": {"sans":["schuage.com"]}}
2024/06/07 14:38:48.601 INFO    autosaved config (load with --resume flag)      {"file": "/root/.config/caddy/autosave.json"}
2024/06/07 14:38:48.601 INFO    serving initial configuration
2024/06/07 14:38:48.603 INFO    tls     storage cleaning happened too recently; skipping for now        {"storage": "FileStorage:/root/.local/share/caddy", "instance": "8631d928-2596-4766-8d0b-f2c2e9035a4d", "try_again": "2024/06/08 14:38:48.603", "try_again_in": 86399.999999051}
2024/06/07 14:38:48.603 INFO    tls     finished cleaning storage units
2024/06/07 14:38:48.603 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0xc000213580"}
2024/06/07 14:38:54.238 DEBUG   events  event   {"name": "tls_get_certificate", "id": "8863eaf1-a190-49de-a3ac-9fcfe13e1601", "origin": "tls", "data": {"client_hello":{"CipherSuites":[23130,4865,4866,4867,49195,49199,49196,49200,52393,52392,49171,49172,156,157,47,53],"ServerName":"schuage.com","SupportedCurves":[10794,25497,29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[1027,2052,1025,1283,2053,1281,2054,1537],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[43690,772,771],"RemoteAddr":{"IP":"76.157.240.140","Port":59908,"Zone":""},"LocalAddr":{"IP":"172.31.29.209","Port":443,"Zone":""}}}}
2024/06/07 14:38:54.238 DEBUG   tls.handshake   choosing certificate    {"identifier": "schuage.com", "num_choices": 1}
2024/06/07 14:38:54.238 DEBUG   tls.handshake   default certificate selection results   {"identifier": "schuage.com", "subjects": ["schuage.com"], "managed": true, "issuer_key": "acme-v02.api.letsencrypt.org-directory", "hash": "baf143dc0e2cfb49985889cc289ebac712f45c1c12311c0642db3662c60bdc6e"}
2024/06/07 14:38:54.238 DEBUG   tls.handshake   matched certificate in cache    {"remote_ip": "76.157.240.140", "remote_port": "59908", "subjects": ["schuage.com"], "managed": true, "expiration": "2024/09/04 20:33:23.000", "hash": "baf143dc0e2cfb49985889cc289ebac712f45c1c12311c0642db3662c60bdc6e"}

Running curl -vL https://schuage.com yields the same response as the Chrome’s console logs. I would share what curl returns but the results are very long.

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

a. System environment:

  • OS:
    • Amazon Linux 2023
  • AMI ID:
    • ami-00beae93a2d981137
  • Size:
    • t2.micro
  • Docker version:
    • Docker version 25.0.3, build 4debf41

b. Command:

curl -o caddy.tar.gz -L "https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_amd64.tar.gz"
tar -zxvf caddy.tar.gz
sudo mv caddy /usr/local/bin/
chmod +x /usr/local/bin/caddy
caddy version

c. Service/unit/compose file:

n/a

d. My complete Caddy config:

{
    debug
}

schuage.com {
    encode gzip
    header {
            Strict-Transport-Security "max-age=31536000;"
            Access-Control-Allow-Origin "*"
    }
    redir https://schuage.com{uri} 302
}

http://schuage.com {
        encode gzip
        header {
                Strict-Transport-Security "max-age=31536000;"
                Access-Control-Allow-Origin "*"
        }
        reverse_proxy {
                to http://localhost:3000
        }
}

5. Links to relevant resources:

I have looked at the following resources:

  1. Need help for run Nextjs App on caddy
  2. 2023 guide for Dockerzing Caddy + Nextjs app

but both of these posts use Docker while I am trying to server my Next.js app with Caddy manually on my AWS EC2 instance within the server.


NOTE: On my NGINX configuration

I can serve my Next.js app from the same Docker container just fine with nginx, but when I do, I don’t NGINX serving my site with QUIC and HTTP/3 in my browser; only HTTP/2, so I want to switch to using Caddy since I can see that it auto-enables HTTP/3 by default with no manual configuration.

For reference, here’s my NGINX configuration that I use:

server {
    root /var/www/html;
    server_name schuage.com www.schuage.com;

    http2 on;            # HTTP/2 only
    http3 on;            # QUIC and HTTP/3 only
    http3_hq on;         # QUIC and HTTP/3 only
    quic_retry on; # QUIC and HTTP/3 only
    ssl_early_data on; # Enabling QUIC 0-RTT

    listen [::]:443 quic reuseport default_server; # QUIC and HTTP/3 only
    listen 443 quic reuseport default_server; # QUIC and HTTP/3 only

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/schuage.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/schuage.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    location / {
        proxy_pass http://localhost:3000;

        add_header Alt-svc 'h3=":443"; ma=3600, h3-29=":443"; ma=3600'; # QUIC and HTTP/3 only
        add_header x-quic 'h3'; # QUIC and HTTP/3 only
        add_header Cache-Control 'no-cache,no-store'; # QUIC and HTTP/3 only
        add_header X-protocol $server_protocol always; # QUIC and HTTP/3 only
        proxy_set_header Early-Data $ssl_early_data; # QUIC and HTTP/3 only
    }
}


server {
    if ($host = www.schuage.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = schuage.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80 default_server;
    listen [::]:80 default_server;
    server_name schuage.com www.schuage.com;
    return 404; # managed by Certbot
}

NOTE: On Inbound connections for my AWS EC2 instance

I am not sure if it is necessary, but I went ahead and changed the Security Group for my EC2 instance by adding an inbound security rule to allow for inbound requests coming from UDP using IPv4.

The rule settings are shown below:

IP    Type        Protocol  Port range  Source
IPv4  Custom UDP	UDP	      443	      0.0.0.0/0

NOTE: On using Dockerized Caddy server to serve my Next.js app

I haven’t tried this yet because I wanted to first try to get my Caddy server running without Docker, but if running a Docker image of a Caddy server is the only way to serve my Docker image of my Next.js app on my AWS EC2 instance, then I will try to do that instead.

For now, I am focusing on other feature integrations before attempting to dockerize a Caddy server to serve my Docker image of my Next.js app.


Any help is very appreciated!

When users connect over HTTPS, you’re serving a redirect to HTTPS with the same domain. That’s an infinite loop. This doesn’t make sense.

You should probably remove all this – you shouldn’t allow access over HTTP.

You should move your reverse_proxy to your first site. Also you can shorten it to:

reverse_proxy localhost:3000
1 Like

Hi Francis,

Running Caddy with the following Caddyfile:

http://schuage.com {
        encode gzip
        header {
                Strict-Transport-Security "max-age=31536000;"
                Access-Control-Allow-Origin "*"
        }
        reverse_proxy http://localhost:3000
}

now results from running curl -vL https://schuage.com are:

* Host schuage.com:443 was resolved.
* IPv6: (none)
* IPv4: 54.198.211.160
*   Trying 54.198.211.160:443...
* connect to 54.198.211.160 port 443 from 10.0.0.181 port 64087 failed: Connection refused
* Failed to connect to schuage.com port 443 after 148 ms: Couldn't connect to server
* Closing connection
curl: (7) Failed to connect to schuage.com port 443 after 148 ms: Couldn't connect to server

Connections to port 443 are being refused

I should also mention that port 443 on in my EC2 instance’s Security Group allows for HTTPS connections on port 443 with the following security rule:

IP    Type   Protocol  Port range  Source
IPv4  HTTPS	 TCP       443	       0.0.0.0/0

Remove http:// from the site block. That’s what I was suggesting. With that config, you’re only listening on port 80 for HTTP, and not HTTPS.

2 Likes

It works! Thank you so much, Francis!

1 Like