Serving pages only from www subdomain and static assets only from static

How would you add a restriction to a Phoenix setup, so that static assets are not available at www.mydomain.tld and dynamic pages not served at static.mydomain.tld?

This is what I use haproxy or nginx, for example, for. Is there a specific need not to use them in this case?

  • redirect all URLs for static content explicitly to static.you.tld
  • redirect all URLs for dynamic content explicitly to www.you.tld
  • bonus, proper TLS support outside of OTP

I wouldn’t bother with different hostnames in this case either, probably, unless its demonstrably making TTFB/TTFP slower, and you want to “shard” static content out to a CDN. But if that’s the case, you wouldn’t be asking this question anyway.

2 Likes

I often use Nginx for such cases. Here’s a sample config:

http {
  upstream phoenix {
    server 192.168.0.2:4000;
    server 192.168.0.3:4000;
  }

  server {
    listen 80;
    server_name www.mydomain.tld;
    location / {
      proxy_pass http://phoenix;
      proxy_redirect off;
      proxy_http_version 1.1;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
      proxy_set_header Host $host;
    }

    # Preventing Phoenix from serving static files
    location ~* \.(css|js|jpg|png|gif|ttf|otf|woff)$ {
      return 404;
    }
  }

  server {
    listen 80;
    server_name static.mydomain.tld;
    etag on;
    location / {
      alias /path/to/static/files/dir/;
    }
  }
}

You also need to add some configuration to the Phoenix app:

config :foo, FooWeb.Endpoint,
    ...,
    static_url: [scheme: "http", host: "static.mydomain.tld", port: 80]
1 Like

Thanks @dch and @Aetherus for your helpful replies. Yes, a reverse proxy gets the job done. I have actually implemented a similar solution in Caddy. However, I thought that this subdomain separation pattern is pretty common and that there might (perhaps even should) be a native Phoenix solution.

For instance, it is my understanding that the Endpoint configuration suggested by @Aetherus only relates to the generation of URLs. Meaning that, if one does not use Router.Helpers.static_url/2 (e.g. only using static_path/2), it seems to be of no use. My expectation would be that a complementary setting would actually determine how static assets are served, as opposed to how URLs are generated.

For inexperienced users, this situation is evidently quite confusing.

You can do this in your endpoint configuration:

static_url: [host: "static.domain.com"]

You can also use put_static_url to configure this dynamically in a request.

It is worth noting though that, if everything is being served by Phoenix, I wouldn’t care about using separate domains. The separate domains were used in the past because browsers would have a limit of connections per domain and that was a way to bypass said limit. I don’t think it is relevant nowadays with HTTP 2.

5 Likes

Thanks for your reply @josevalim. Rethinking having separate domains under HTTP/2 is good advice. In my case, static is used as a cookieless domain (for geographic locations opted out of CDN), even though the scope of cookieless domains is itself debatable.

BTW, I have just (re-)tested the static_url endpoint configuration setting, and it does not prevent the serving of static assets from other subdomains, such as www. This of course is entirely consistent with the documentation, which reads “configuration for generating URLs for static files”.

@konstantine oh, if you want to filter the serving per host, you can do this:

@my_static Plug.Static.init(your: options)
plug :filtered_static

def filtered_static(%{host: "assets.mydomain.com"} = conn, _opts), do: Plug.Statica.call(conn, @my_static)
def filtered_static(conn, _opts), do: conn
2 Likes

Excellent, I will try it out, thank you!

This is a common use case. I agree that we don’t really need subdomains to get around concurrent request issues anymore. However, it’s definitely the most convenient/ergonomic way to serve your static assets through a CDN:

  1. Point assets.myapp.com DNS at a CDN
  2. Point CDN at application
  3. Configure endpoint static_url to assets.myapp.com

Now my application doesn’t need to deal with serving requests for static assets, those can be cached at the CDN level.

I’m in the process of setting this up right now, and this conversation was helpful to point me in the right direction.