Angular SPA - Navigation works, refresh shows 404

1. The problem I’m having:

I use a Caddyfile from the API Platform samples. My backend is Symfony, my frontend is Angular. When I call the site, it shows /login and I can navigate through various pages. But once I hit refresh on any frontend route, it gives me an (nginx) 404 Error Page.

2. Error messages and/or full log output:

404 Page

3. Caddy version:

2.7

4. How I installed and ran Caddy:

Via Docker Compose

a. System environment:

Debian with a Docker Environment

b. Command:

docker compose up -d

c. Service/unit/compose file:

d. My complete Caddy config:

{
    # Debug
    {$CADDY_DEBUG}
}

{$SERVER_NAME}

log

@pwa expression `(
        header({'Accept': '*text/html*'})
        && !path(
            '/docs*', '/graphql*', '/bundles*', '/contexts*', '/_profiler*', '/_wdt*', '/api*', '/auth',
            '*.json*', '*.html', '*.csv', '*.yml', '*.yaml', '*.xml'
        )
    )
    || path('/assets*', '/favicon.ico', '/manifest.json', '/robots.txt', '/*.css', '/*.js', '/sitemap*', '*.webmanifest', '/ngsw.json', '/login')`

route {
    root * /srv/app/public
    mercure {
        transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
        publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
        subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
        anonymous
        subscriptions
        {$MERCURE_EXTRA_DIRECTIVES}
    }
    vulcain

    header ?Link `</docs.jsonld>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation", </.well-known/mercure>; rel="mercure"`
    header ?Permissions-Policy "browsing-topics=()"

    reverse_proxy @pwa http://{$PWA_UPSTREAM}

    php_fastcgi unix//var/run/php/php-fpm.sock
    encode zstd gzip
    file_server
}

5. Links to relevant resources:

Docker Compose File

version: "3.6"
services:
  ofelia:
    image: mcuadros/ofelia:latest
    depends_on:
      - php
    command: daemon --docker
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
  php:
    build:
      context: .
      target: app_php
      args:
        SYMFONY_VERSION: ${SYMFONY_VERSION:-}
        STABILITY: ${STABILITY:-stable}
    restart: unless-stopped
    volumes:
      - php_socket:/var/run/php
      - jwt:/srv/app/config/jwt
    healthcheck:
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 30s
    environment:
      # Run "composer require symfony/orm-pack" to install and configure Doctrine ORM
      DATABASE_URL: postgresql://${POSTGRES_USER:-app}:${POSTGRES_PASSWORD:-!ChangeMe!}@database:5432/${POSTGRES_DB:-app}?serverVersion=${POSTGRES_VERSION:-15}
      TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
      TRUSTED_HOSTS: ^${SERVER_NAME:-example\.com|localhost}|caddy$$
      MERCURE_URL: ${CADDY_MERCURE_URL:-http://caddy/.well-known/mercure}
      MERCURE_PUBLIC_URL: https://${SERVER_NAME:-localhost}/.well-known/mercure
      MERCURE_JWT_SECRET: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
  pwa:
    build:
      context: .
      target: app_ui
    environment:
      NEXT_PUBLIC_ENTRYPOINT: http://caddy
  caddy:
    build:
      context: .
      target: app_caddy
    depends_on:
      - php
      - pwa
    environment:
      PWA_UPSTREAM: pwa:80
      SERVER_NAME: ${SERVER_NAME:-localhost}, caddy:80
      MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
      MERCURE_SUBSCRIBER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET:-!ChangeThisMercureHubJWTSecretKey!}
    restart: unless-stopped
    volumes:
      - php_socket:/var/run/php
      - caddy_data:/data
      - caddy_config:/config
      - ./docker/php/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    ports:
      # HTTP
      - target: 80
        published: ${HTTP_PORT:-80}
        protocol: tcp
      # HTTPS
      - target: 443
        published: ${HTTPS_PORT:-443}
        protocol: tcp
      # HTTP/3
      - target: 443
        published: ${HTTP3_PORT:-443}
        protocol: udp
  
  # Mercure is installed as a Caddy module, prevent the Flex recipe from installing another service
  ###> symfony/mercure-bundle ###
  ###< symfony/mercure-bundle ###
  
  ###> doctrine/doctrine-bundle ###
  database:
    image: postgres:${POSTGRES_VERSION:-15}-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-app}
      # You should definitely change the password in production
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
      POSTGRES_USER: ${POSTGRES_USER:-app}
    volumes:
      - database_data:/var/lib/postgresql/data:rw
###< doctrine/doctrine-bundle ###
volumes:
  php_socket:
  caddy_data:
  caddy_config:
  jwt:
  ###> symfony/mercure-bundle ###
  ###< symfony/mercure-bundle ###
  
  ###> doctrine/doctrine-bundle ###
  database_data:
###< doctrine/doctrine-bundle ###

My Nginx Config

server {
    listen 80;
    server_name my-website.com;

    location / {
        proxy_pass http://caddy:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Your angular app probably needs to be configured to respond with the index.html for requests to other URLs. I don’t think this is a problem with Caddy. The 404 comes from upstream (your pwa app).

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