2

I have a fairly complex Ruby application that gives customers a dashboard available under their own chosen subdomain. i.e.: http://mycompany.app.com, http://myproject.app.com.

I also have the product website running on the root domain (i.e., http://app.com) and I've bought and configured an SSL certificate with Nginx and it's working as expected, but that leaves me with the following problem scenario:

I need to redirect all non-https traffic to the https version of the page, except for any requests going to any of the subdomains. What makes it tricky is that I do however need to redirect the www version of the site to the non-www version.

http://app.com             -> https://app.com
http://www.app.com         -> https://app.com
http://nike-admin.app.com !-> https://nike-admin.app.com

Here's what I have come up with so far in nginx.conf for this app (real name replaced by app):

upstream unicorn_server {
  server unix:/var/www/<app>/tmp/sockets/unicorn.sock fail_timeout=0;
}

server {
  listen 80;
  listen [::]:80 default_server ipv6only=on;
  server_name <app>.co;

  location / {
    rewrite ^ https://$server_name$request_uri permanent;
  }
}

server {
  server_name <app>.co;
  root /var/www/<app>.co/public;

  client_max_body_size 4G;
  keepalive_timeout 70;
  listen 443 ssl;
  ssl_certificate /etc/nginx/ssl/<app>.crt;
  ssl_certificate_key /etc/nginx/ssl/<app>.key;

  location / {
    try_files $uri @app;
  }

  location @app {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn_server;
  }
}

In the first server{} block, I explicitly listen for any connection on port 80 and redirect those to the https version, but that's a double-edged sword as the SSL certificate only covers the apex domain. I don't want requests on any subdomains other than www to be redirected to the https equivalent.

I could potentially use regex, but from what I've seen online, but it seems to be frowned upon?

Is there any other way to do this?

Sam
  • 7,252
  • 16
  • 46
  • 65
ghstcode
  • 2,902
  • 1
  • 20
  • 30
  • *"I could potentially use regex, but from what I've seen online, but it seems to be frowned upon?"* -- What do you mean? I wouldn't frown upon using RegEx for this. Where'd you see that? – Casey Falk Aug 13 '14 at 16:46
  • Regular expression engines are finite state, and most of the time you'll find writing a few lines of regex easier to maintain than several blocks of code anyway. For dedicated services you'll be using a lot, it's true that performance matters, but for this case, use regex. – Unihedron Aug 16 '14 at 01:22
  • any particular reason to have non-HTTPS for applications? do you want cookies to be stolen? – Anatoly Aug 18 '14 at 19:37
  • @mikhailov the dashboards on the subdomains are disposable read only with non-sensitive data, so the cost of a wildcard SSL cert doesn't make sense in this case. – ghstcode Aug 20 '14 at 09:14

3 Answers3

4

You can use the below regex:

/^.*?\b(?!www\.)((?:[\w-]+\.)+[\w-]+).*$/

Replace with:

https://$1

Here is a regex demo!

  • ^ Asserts position at the start of string.
  • .*? Matches any number of characters except new line in lazy match. (Char by char)
  • \b Asserts a word boundary position (\w\W or \W\w.)
  • (?! Negative lookahead: Asserts that our position is NOT:
    • www\. The character sequence "www.".
  • ) Then carry on the match:
  • ( Opens a capturing group. This is so we can use a back-reference in the replacement.
    • (?: Opens a non-capturing group.
      • [\w-]+\. Any sequence of word characters or hyphens (because hyphens may be in domain names) followed by a dot.
    • )+ One or more groups.
  • [\w-]+ Another group of word characters or hyphens.
  • ) Closes the group.
  • .* Matches the rest of the string but it's not part of the domain, hence ignored.
  • $ Asserts position at end of string. This anchor may not be necessary, but it's good to have for readability.

Read more:

Community
  • 1
  • 1
Unihedron
  • 10,902
  • 13
  • 62
  • 72
2
(http:\/\/(www\.)?)([a-zA-Z-]+\.)(?![a-zA-Z-]+\.)

This will match:

  1. Anything that uses http; or
  2. Anything that starts with www.

This will NOT match:

  1. Anything that has a subdomain.

If you wanted to do a ruby replacement, here are some examples (in irb):

http://app.com (switches to https)

2.1.1 :047 > "http:\/\/app.com".sub /(http:\/\/(www\.)?)([a-zA-Z-]+\.)(?![a-zA-Z-]+\.)/, 'https://\3'
 => "https://app.com" 

http://www.app.com (switches to https)

2.1.1 :046 > "http:\/\/www.app.com".sub /(http:\/\/(www\.)?)([a-zA-Z-]+\.)(?![a-zA-Z-]+\.)/, 'https://\3'
 => "https://app.com" 

http://subdomain.app.com (stays the same)

2.1.1 :045 > "http:\/\/subdomain.app.com".sub /(http:\/\/(www\.)?)([a-zA-Z-]+\.)(?![a-zA-Z-]+\.)/, 'https://\3'
 => "http://subdomain.app.com"
Unihedron
  • 10,902
  • 13
  • 62
  • 72
Jean
  • 670
  • 1
  • 5
  • 14
1

I use this construction and it works fine:

server {
        listen 80;
        server_name my.site.ru www.my.site.ru;
        rewrite ^(.*)$ https://my.site.ru$1 permanent;
}

I also have _http://(www.)site.ru http(s)://(www.)othermy.site.ru domains and it's ok.

Starting from version 0.8.4 it should be better to use

        return          301             https://$http_host$request_uri;
user3132194
  • 2,381
  • 23
  • 17