2

I'm trying to remove the www from my request urls but when I add the following to my nginx config my AWS ELB health check fails.

server {
    listen 80;
    server_name www.example.com;
    return 301 $scheme://example.com$request_uri;
}

Any idea what could be causing the issue? This is the rest of my config:

server {

    listen 80;

    server_name .example.com;
    root /var/www/static;
    index index.html;

    try_files $uri.html $uri $uri/ =404;

    charset UTF-8;

    error_log /var/log/error.log;
    access_log /var/log/access.log;

    # ELB test route
    location /test {
        add_header X-Robots-Tag noindex;
        default_type text/plain; 
        return 200 'success';
    }

    # Redirect any request http request that come from AWS ELB to https
    if ($http_x_forwarded_proto = "http") {
        return 301 https://$server_name$request_uri;
    }
}

Here is my Health Check setup:

aws target group health check

5
  • 1
    My initial guess is the ELB health checks are configured to look at the www domain and doesn't follow 301 directs. Please post a screenshot of the ELB health check configuration. Curious why your "server_name" domain has a dot at the start as well, just make it "example.com" (I changed your server name as per standards).
    – Tim
    Apr 5 '17 at 2:21
  • @Tim the . is there so nginx handles any subdomains; it basically combines example.com *.example.com.
    – jwerre
    Apr 5 '17 at 2:30
  • I haven't used that, I configure subdomains individually. www is a subdomain, but Nginx typically uses the most specific server / location it can so should be ok. Based on that ELB configuration I suggest you ensure the health check is a location that isn't being redirected.
    – Tim
    Apr 5 '17 at 2:43
  • @Tim /test shouldn't redirect since it return 200 'success'; before the redirect. I could be mistaken on that; can you think of any reason why /test would be redirected to https?
    – jwerre
    Apr 5 '17 at 16:43
  • Just be totally sure that the ELB is hitting the non-www root domain, and that it's getting 200 not 301. If it is then we'll have to dig deeper. This would be easier if we knew the domain name.
    – Tim
    Apr 5 '17 at 19:05
2

From the Amazon docs:

To ensure that your load balancer can scale properly, verify that each subnet for your load balancer has a CIDR block with at least a /27 bitmask (for example, 10.0.0.0/27) and has at least 8 free IP addresses. Your load balancer uses these IP addresses to establish connections with the instances.

This makes it sound like the ELB health check is made via AWS private IP rather than domain name. From what I know of Amazon, that's how they prefer to do things when they can (for obvious performance reasons, no need for DNS lookup, can be handled by closer routers, etc.)

If that's the case, then the host header (unless it's blank, I'm not 100% sure how host headers work when you're making a request to a bare IP) will be the private AWS address of the EC2 instance (I'm assuming it's EC2) running the webserver. This impacts the way Nginx chooses what virtual server should handle the request.

Since Nginx has two server blocks, it has to decide which one gets any given request. The process by which it chooses server blocks (details here) is as follows:

  • First it looks for servers listening on the port on which the request came in. In this case, both servers are listening on the same port, so this doesn't help any.

  • Then it looks at the host header of the request, and looks for servers with a server_name that matches the host header. In this case, none of them will match, because the host header is the private AWS IP assigned to the instance (or blank, as I said I'm not sure.)

  • Finally, if neither of those yields a match, it just goes with whichever server is the default. In the absence of an explicit default declaration, the server which is defined first in the site config is chosen as the default.

If your redirect server comes before the main server config (as I would imagine it does) then this is probably your problem. You need to do one of three things:

  • Specify that you want the other server (the main server) to be the default by adding default_server to the listen directive, i.e. listen 80 default_server;

  • Switch the location /test block to the other server block, or

  • Reverse the order in which the server blocks are defined.

The first option seems like the way to go, since it makes sense for the default_server directive to be explicit rather than implicit, and it makes sense for the main server to be the default rather than the you-had-one-job redirect server.

If I'm correct in my hypothesizing, that should solve it.

4
  • The request is either to the www or non-www domain name (we don't have that information). I think either the www server or the other server will match, so I don't think it'll go to the default server.
    – Tim
    Apr 5 '17 at 5:01
  • @Tim found more explicit info, see edit. I'm not entirely sure how host headers work when you're making a request to a bare IP, but it seems unlikely that it would contain helpful information. It would be odd for the ELB to make its health checks to www.domain.com by default anyway - what if you're not running your site at www? Apr 5 '17 at 5:22
  • 2
    adding the default_server did the trick. Thanks @JosephMontanaro
    – jwerre
    Apr 5 '17 at 19:11
  • That adding default_server fixed this indicates your configuration wasn't quite right. One of your server_name statements is likely incorrect or incomplete. You could tell which by logging requests and seeing what domain it requests, then adding this to the appropriate Nginx server block. However if default_server works it might not be worth the bother, though changes in future like adding domains could break it.
    – Tim
    Apr 7 '17 at 1:53
0

I can advise followings from experience of AWS ELB I/F configuration

  1. Define location / {} block
  2. You had better use location = /test {} block exactly URL match
  3. You had better use special server_name _ and write location = /test {} block.

ref:
http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name


server {

    listen 80;

    # changed for 1. "anonymous server" (without Host: header) will accept request.
    server_name _ .example.com;
    root /var/www/static;
    index index.html;

    try_files $uri.html $uri $uri/ =404;

    charset UTF-8;

    error_log /var/log/error.log;
    access_log /var/log/access.log;


    # changed for 1. defined location / {} for default behavior
    location / {
        try_files $uri $uri/ =404;
    }
    # ELB test route
    # changed for 2. exactly match only /test request
    location = /test {
        add_header X-Robots-Tag noindex;
        default_type text/plain; 
        return 200 'success';
    }

    # Redirect any request http request that come from AWS ELB to https
    if ($http_x_forwarded_proto = "http") {
        return 301 https://$server_name$request_uri;
    }
}
3
  • I'm not sure I understand your answer. An example may be more helpful.
    – jwerre
    Apr 5 '17 at 6:32
  • What is the purpose of putting try_files in a location block?
    – jwerre
    Apr 7 '17 at 15:17
  • try_files defines (and controls) the order of requested URL resolvant. The directive is useful If you want to specify .php, .html, .htm, and so on. For more information: nginx.org/en/docs/http/ngx_http_core_module.html#try_files
    – minish
    May 8 '17 at 5:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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