Naked Domain redirect to www with kubernetes nginx

Background

Here at DevGurus.io, one of our customers had the following setup:

  • DNS records managed in GoDaddy
  • 3 DNS A records with “@” name and with Google IPs value (216.239.32.21, 216.239.34.21, 216.239.36.21). Usually these are set when Google Suite or Google Cloud Service is configured for a Domain (more information here). These are the IP´s you would get if you do a nslookup example.com
  • website was actually in “www” subdomain, with a CNAME record pointing to a url from CDN provider. This means, when you access www.example.com you are actually accessing something like url-from-cdn.cdnprovider.com or whatever URL your CDN provider gives. Here if you do a nslookup www.example.com you would get something like this:
#nslookup www.example.com
Non-authoritative answer:
www.example.com canonical name = url-from-cdn.cdnprovider.com.
Name: url-from-cdn.cdnprovider.com
Address: 11.22.33.44

Noticed canonical name ? That’s actually CNAME

So, if final users try to access the site with any possible combination:

  1. http://example.com
  2. http://www.example.com
  3. https://example.com

It should be redirected to https://www.example.com (this would technically be possibility #4).

Problem

Option #1 works, since there is a 301 redirection in place (this comes from Google, notice “Server: ghs”):

#curl -I example.com
HTTP/1.1 301 Moved Permanently
Location: http://www.example.com
Date: Wed, 15 Aug 2018 18:27:55 GMT
Content-Type: text/html; charset=UTF-8
Server: ghs
Content-Length: 234
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN

Option #2 works, since subdomain www actually takes you to CDN, and in there we have an enforcement to redirect you to https if you are coming from http .

But the problem is in #3:

#curl -I https://example.com
curl: (7) Failed to connect to example.com port 443: Operation timed out

If you try to browse it, you would get site can't be reached error.

Alternatives

Google / G-Suite / Google Domain

Since the A records are currently pointing to Google IPs, our first option was to change settings there, to update that 301 Redirect rule for HTTPS, and upload our SSL Certificate to enable 443 port.

Unfortunately we found no way of doing that, so we analyze the following alternative.

GoDaddy

Currently GoDaddy does not offer ALIAS DNS record, and we cannot setup CNAME for Naked Domain because of RFC1034 (section 3.6.2).

An ALIAS record is a virtual record type, which we could say is kinda a mixture of A and CNAME, where following our example, we could add a record for the naked domain example.com to url-from-cdn.cdnprovider.com and if we do a nslookup example.com we would see the ip from url-from-cdn.cdnprovider.com . So its like having an A record, that automatically updates the IP value.

However, they do offer a Forwarding section:

While applying this, the A records get removed and only 1 A record is added, with an IP from GoDaddy. But we get almost same results than before:

#curl -I example.com
HTTP/1.1 301 Moved Permanently
Server: nginx/1.12.2
Date: Thu, 16 Aug 2018 18:10:16 GMT
Content-Type: text/html; charset=utf-8
Connection: close
Location: https://www.example
#curl -I https://example.com
curl: (7) Failed to connect to https://example.com port 443: Operation timed out

Note: After several support contact with GoDaddy, they are not able to add our SSL Cert (which we bought it from them), which would solve the issues. And yes, they use a nginx as well.

A Record with IP from CDN

Technically, what we could do, is create an A record with the IP we got from CDN Provider, 11.22.33.44. This might work, but it’s not recommended at all…because we don’t have control at all of that IP. If our CDN provider changes that IP, we will have to manually update the A Record, with downtime until we do it and propagates.

Solution

We ended up deploying our own nginx server in our GCP Kubernetes Cluster, with a STATIC IP Address we reserve in GCP, which we have control and we know it won’t change.

Note: You could opt for the Cloud Provider/Source Repository/CI of your choice

We will try to sum up the steps we did to deploy the solution:

  1. Get the latest helm chart for nginx-ingress (we like to use Helm, as you can see in the chart.yaml file the source is kubernetes/ingress-nginx)
  2. We use Bitbucket as source repository with Bitbucket pipelines as CI, so we created a new repository for this.
  3. We reserve an static IP in GCP that we are going to use in the nginx controller service (LoadBalancer type). IMPORTANT: This IP needs to be regional, and in same region than the GKE Cluster.
  4. Your helm customization file should look something like this:
controller:
service:
loadBalancerIP: 123.45.67.89
externalTrafficPolicy: Local
replicaCount: 2
ingress:
enabled: true
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($http_x_forwarded_proto != 'https') {
return 301 https://www.$host$request_uri;
}
tls:
- hosts:
- example.com
secretName: secret-with-tls

rules:
- host: example.com
http:
paths:
- backend:
serviceName: default-backend
servicePort: 80
path: /

Being 123.45.67.89 the IP reserved in step #4. You need to specify either a rule or backend, even though it will not route it there because the redirection happens before.

There are many other options/features you could enable/configure. Either if you want a different redirection, or if you don’t want to redirect maintaining the request_uri …anything that matches your needs!

5. Once this is successfully deployed, you could test functionality using curl:

curl -Ik https://123.45.67.89 -H 'Host: example.com'
HTTP/2 301
server: nginx/1.13.12
date: Thu, 16 Aug 2018 18:24:00 GMT
content-type: text/html
content-length: 186
location: https://www.example.com

And also validate, that is including the request_uri :

curl -Ik https://123.45.67.89/login -H 'Host: example.com'
HTTP/2 301
server: nginx/1.13.12
date: Thu, 16 Aug 2018 18:24:00 GMT
content-type: text/html
content-length: 186
location: https://www.example.com/login

7. Once you finished validation, you are ready to update the A record in your DNS, using the IP we got from step #4.

8. Once DNS propagation is over (we recommend setting a small value, in GoDaddy you could set it to 600 seconds )

And thats it!

Hope you found this article useful, please feel free to comment