45

I have an application written in Angular 7 that I am deploying to a Docker container with NGINX. When I run the container, everything works perfectly except that if i Refresh the page in the browser (F5) I get an NGINX 404 error page.

Here is my nginx.conf file from which you can see ive tried "try_files"

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    gzip  on;

    include /etc/nginx/conf.d/*.conf;

    server {
        listen 80; 

        location / {
            root /usr/share/nginx/html;
            index index.html;
            try_files $uri /index.html;
        }
    }
}

My Dockerfile:

FROM node:alpine as builder
RUN apk update && apk add --no-cache make git

WORKDIR /app

COPY package.json package-lock.json /app/
RUN cd /app && npm install

COPY .  /app
RUN cd /app && npm run build

FROM nginx:alpine

RUN rm -rf /usr/share/nginx/html/* && rm -rf /etc/nginx/nginx.conf
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /app/dist/hyper-client-admin /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Directory on the deployed container is:

/usr/share/nginx/html # ls -la
total 23564
drwxr-xr-x    1 root     root          4096 May 20 00:18 .
drwxr-xr-x    1 root     root          4096 Mar  8 03:05 ..
drwxr-xr-x    2 root     root          4096 May 20 00:18 assets
-rw-r--r--    1 root     root        290728 May 20 00:18 es2015-polyfills.js
-rw-r--r--    1 root     root        211178 May 20 00:18 es2015-polyfills.js.map
-rw-r--r--    1 root     root           997 May 20 00:18 favicon.png
-rw-r--r--    1 root     root           770 May 20 00:18 index.html
-rw-r--r--    1 root     root        114749 May 20 00:18 main.js
-rw-r--r--    1 root     root        115163 May 20 00:18 main.js.map
-rw-r--r--    1 root     root        241546 May 20 00:18 polyfills.js
-rw-r--r--    1 root     root        240220 May 20 00:18 polyfills.js.map
-rw-r--r--    1 root     root          6224 May 20 00:18 runtime.js
-rw-r--r--    1 root     root          6214 May 20 00:18 runtime.js.map
-rw-r--r--    1 root     root       1117457 May 20 00:18 styles.js
-rw-r--r--    1 root     root       1191427 May 20 00:18 styles.js.map
-rw-r--r--    1 root     root      10048515 May 20 00:18 vendor.js
-rw-r--r--    1 root     root      10505601 May 20 00:18 vendor.js.map

And here is the console output:

172.17.0.1 - - [20/May/2019:00:18:30 +0000] "GET / HTTP/1.1" 200 371 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"lopment\hyper-client-admin>
172.17.0.1 - - [20/May/2019:00:18:30 +0000] "GET /runtime.js HTTP/1.1" 200 6224 "http://localhost:81/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:30 +0000] "GET /polyfills.js HTTP/1.1" 200 241546 "http://localhost:81/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:30 +0000] "GET /main.js HTTP/1.1" 200 114749 "http://localhost:81/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:30 +0000] "GET /styles.js HTTP/1.1" 200 1117457 "http://localhost:81/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:30 +0000] "GET /vendor.js HTTP/1.1" 200 10048515 "http://localhost:81/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:31 +0000] "GET /assets/logo-white.svg HTTP/1.1" 200 4519 "http://localhost:81/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:31 +0000] "GET /favicon.png HTTP/1.1" 200 997 "http://localhost:81/login" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
172.17.0.1 - - [20/May/2019:00:18:35 +0000] "GET /login HTTP/1.1" 404 188 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36" "-"
2019/05/20 00:18:35 [error] 6#6: *4 open() "/usr/share/nginx/html/login" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /login HTTP/1.1", host: "localhost:81"

Any ideas whats going on here?

UPDATE: The actual answer to this lies in the comments of @Rajesh's Answer. The issue is that I was working on /etc/nginx/nginx.conf and I needed to be working on /etc/nginx/conf.d/default.conf

1
  • 2
    Would be nice if the down voter could clarify whats wrong with the question so that it can be improved. Commented May 21, 2019 at 0:36

6 Answers 6

100
+50

With a refresh on Angular app, we need to tell nginx web server to first look at the index.html file if the requested route exists or not before showing the error page. This is working fine for me:

nginx.conf

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

Dockerfile

FROM node:16-alpine as node
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build --prod

FROM nginx:alpine
COPY ./nginx.conf /etc/nginx/conf.d/default.conf # Not /etc/nginx/nginx.conf
COPY --from=node /app/dist/myapp /usr/share/nginx/html
9
  • 1
    Please take a look at my nginx.config file. I have try_files in there already. It doesn't work. Note I dont have the "folder" check but mine is a valid value according to the documentation "Using try_files means that you can test a sequence. If $uri doesn’t exist, try $uri/, if that doesn’t exist try a fallback location." and "If you don’t care about checking for the existence of directories, you can skip it by removing $uri/." - nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls Commented May 20, 2019 at 22:02
  • Sorry not sure what you comment means? Whats not correct? You tell me to use try_files. with "try_files $uri $uri/ /index.html;" or "try_files $uri index.html;", which are both correct, I still have this issue. Your answer serves no purpose other than to state what I am already doing. Commented May 22, 2019 at 4:42
  • let me update the answer and see if it works. it seems that ordering also matters in the location block..not sure..we can try..i have this working on my end..so you can try the same location block Commented May 22, 2019 at 4:44
  • 2
    just came to that exact same conclusion. The issue is that I was working on /etc/nginx/nginx.conf and I needed to be working on /etc/nginx/conf.d/default.conf Commented May 22, 2019 at 5:14
  • 4
    Changing try_files $uri $uri/ =404; to try_files $uri $uri/ /index.html; worked for me Commented Jul 30, 2021 at 18:42
12

this likely can be fixed quickly by simply using the useHash: true flag. For some unknown reason angular does not default this setting to true.

Make sure your app-routing-module.ts file contains useHash like this:

@NgModule({
  imports: [RouterModule.forRoot(routes, { useHash: true })],
  exports: [RouterModule]
})
0
5

In my case, I have one NGINX fronting a group of NGINX' as a proxy, and I had to add =404 to avoid a redirect loop:

location / {
    root        /usr/share/nginx/html;
    index       index.html;
    try_files   $uri $uri/ /index.html =404;
}
4

I have an experience to add to this, I faced this recently with one of my applications where none of this seems to work, and it make me stumble upon records that the Nginx is slightly different with each release.

In my case, what solved the issue was a 404 fallback of the form below in the /etc/nginx/conf.d/default.conf due to inherent war my nginx.conf was written.

Below is the default.conf

server {
    listen       80;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        try_files $uri $uri/ /index.html =404;
        index  index.html index.htm;
    }
}

nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    //This here takes the deafaut.conf alongside
    //possibly overwriting the server markings in the nginx.conf

    include /etc/nginx/conf.d/*.conf; 

    sendfile        on;

    keepalive_timeout  65;

    gzip  on;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            try_files $uri $uri/ /index.html =404;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    }
}

You can typically remove the line to not take into consideration the default.conf or any other files and that would be fine for most dockerized containers(not in my case though!) and seems to work for many other scenarios.

3

The answer of Rajesh is great, but I think it should be extended with some code.

With the current answer if, for example, a javascript file can't be found, the index.html is returned instead, which can lead the browser to cache the index.html file under the name of the javascript file you requested. If this happens your app will most likely crash and you need to clear the cache to get it working again.

To prevent this you can add a location block for the javascript files that will return a 404 if this file isn't found. See code below.

server {
    listen       80;
    server_name  localhost;
    root   /usr/share/nginx/html;

    
    # Add additional extension if you need it 
    location ~ \.(js)$ {
      try_files $uri $uri/ =404;
    }

    location / {
        try_files $uri $uri/ /index.html;
        index  index.html index.htm;
    }
}

Note: This example is with javascript files but you can extend the regex in the location block with any file extensions you want to have the 404 behaviour for when the file isn't found.

-1

Under the official docs, you can find the explanation about 404 - PageNotFound issue and the fallback confiug solution for different kind servers like appache, nginx, etc.

For nginx the solution is Front Controller Pattern Web Apps

try_files $uri $uri/ /index.html;

My config for nginx default.conf

server { # simple reverse-proxy

  # listen on port 80
    listen 80;

  # serve static files (location of builded application source code)
    root /usr/share/nginx/html;

  # fallback set to fix 404 when page reload
  location / {
    try_files $uri $uri/ /index.html;
  }

  # pass requests to the gateway
    location /api {
        proxy_pass http://SERVICE:PORT;
    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.