Nginx 403 Error on Specific Angular Route

So I've got an angular app that has a few routes that I can easily get to.

However, /admin eludes me as I get a 403 error. An example of a working route, set up the exact same way at /articles loads just fine.

Here's my app config:

    app.config(['$stateProvider', '$locationProvider', '$urlRouterProvider',
    function($stateProvider, $locationProvider, $urlRouterProvider) {
        $stateProvider.
            ...
            state('articles', {
                url: '/articles',
                templateUrl: 'components/articles/templates/articles.tmpl.html',
                controller: 'ArticleController'
            }).
            state('articles/:id', {
                url: '/articles/:id',
                templateUrl: 'components/articles/templates/single-article.tmpl.html',
                controller: 'ArticleController'
            }).
            state('admin', {
                url: '/admin',
                templateUrl: 'admin/templates/admin.tmpl.html',
                controller: 'AdminController'
            }).
            state('admin.create', {
                url: '/create',
                templateUrl: 'admin/create/templates/create.tmpl.html',
                controller: 'AdminCreateController'
            }).
            ...
        $urlRouterProvider.otherwise('/');
        $locationProvider.html5Mode(true);
    }
]);

I know the culprit is Nginx, and I know it's triggered with html5Mode; so I checked out how I'm re-routing to the index page and it all looks good (and works properly on the site) with this as my nginx sites-available conf:

server {

   listen 80;
   server_name app.local.dev;
   root "/vagrant/app";
   index index.html;
   location / {
       try_files $uri $uri/ /index.html;

   }

}

Here's an example of of error log for Nginx:

2015/02/06 06:14:23 [error] 3361#0: *20 directory index of "/vagrant/app/admin/" is forbidden, client: 192.168.99.1,

I don't appear to have any permissions oddities with my file structure (the root folder where /admin files live:

-rw-r--r-- 1 vagrant vagrant  229 Feb  2 20:33 AdminController.js
-rw-rw-r-- 1 vagrant vagrant   90 Feb  2 05:18 AdminModule.js
drwxr-xr-x 1 vagrant vagrant  204 Feb  3 02:55 create
-rw-r--r-- 1 vagrant vagrant 6148 Feb  2 16:08 .DS_Store
drwxr-xr-x 1 vagrant vagrant  170 Feb  3 05:12 edit
drwxr-xr-x 1 vagrant vagrant  102 Feb  2 20:33 templates

/admin template file:

-rw-r--r-- 1 vagrant vagrant 151 Feb  2 20:33 admin.tmpl.html

Root folder where /articles files live:

-rw-r--r-- 1 vagrant vagrant 715 Feb  5 03:47 ArticleController.js
-rw-r--r-- 1 vagrant vagrant  94 Dec  3 18:29 ArticleModule.js
drwxr-xr-x 1 vagrant vagrant 136 Feb  6 04:19 templates

/articles templates files:

-rw-r--r-- 1 vagrant vagrant 526 Feb  6 04:19 articles.tmpl.html
-rw-r--r-- 1 vagrant vagrant 453 Feb  5 22:35 single-article.tmpl.html

So things are re-routing and writing correctly. I can hit my main domain, get my homepage without a hash and click around and maintain a pretty url that contains no hash (html5Mode) and (aside from /admin) be able to hit refresh and re-load that state correctly through Angular.

However, if I change in my app.config the admin state to a url of /admins it will magically work, reload will refresh the /admins correctly and show me the right view.

state('admin', {
    url: '/admins', // works as expected and loads what was my /admin view with correct refreshing in Nginx
    templateUrl: 'admin/templates/admin.tmpl.html',
    controller: 'AdminController'
}).

So is there some sort of weird reserved URL in Nginx? I'm using Vagrant with this and have specifically turned off sendfile as many report odd caching. Is my nginx.conf set up wrong? I feel like my URL rewriting is working correctly since it's working everywhere else on the site. The only other difference between a route that works /articles and one that doesn't /admin is that /admin has nested states inside of it, eg. /admin/create/pages/.

It seems your logical path clashes with your "physical" path.

UiRouter intercepts requests on the client. Every time you try to get "foo.bar/admin" it checks on a routing table and says "uhm, this is registered as a logical path (client side), so it doesn't need to query the server". The problem is, when you request directly "foo.bar/admin" instead of navigating to it, nginx is the first to get the request (since uiRouter would't even be loaded), and since you are using the same path in your server for storing files, instead returning the index.html file, your other

rules try_files $uri $uri/

has precedence and attempts to find it on your server. But of course, it doesn't find it because there is no 'admin' file there. I believe it's not a 404 because the path actually exists on the server, but is a directory and therefore it cannot return it, so it sens a 403 instead.

One possible solution is just avoid path collision, like you did when renaming 'admin' to 'admins'. Since nginx doesn't find 'admins' on the server, it fallbacks to index.html.

Other solution would be to use something like gulp or grunt to compress your files and avoid having several directories (and therefore path collision).

And last, you could rewrite some rule in order to redirect /admin to index.html... but I would't go with that. The other two are far better options.

Sadly I don't have much experience with angular, but I could say that when you request a url like /admin nginx checks the rules you specified in the try_files, which are

$uri # check if there's a file called admin

of course you don't have one so it goes to the next step

$uri/ #check if there's a directory called admin

And it's found, and since you only supplied a directory so it goes to check the index from

index index.html;

so nginx trys to find /admin/index.html and I assume it doesn't exist, so then nginx falls back to the last thing which is directory listing, but since you don't have autoindex on then it falls back to a 403 error because directory index is not allowed.

If you want to skip this step and serve index.html I assume it has some code that is related to angular, then my suggested solution would be removing the $uri/ part entirely, so the final try_files line would be

try_files $uri /index.html;