Play Framework 2 in production: HTTPS using nginx

If you provide some sort of user login on your page, you’ll probably want to use HTTPS/SSL. Play Framework 2 supports secure connections using the recommended setup with a reverse proxy such as nginx.

This means your application server will run your play framework application on a private port (the default is 9000), as well as an nginx web server which listens to the public ports (HTTP/80 and HTTPS/443), and redirects those requests back to your play app.

Nginx Configuration for HTTPS

Installing nginx on most linux distributions is usually a one-liner (please refer to the nginx documentation). Here is the configuration we use for setting up nginx as the reverse proxy. In this example we assume that your domain name is example.com and that your compiled Play application files (using play dist) are located in /var/example/target.

UPDATE: The some paths may have changed for Play Framework 2.3+ and later. See comments.

Nginx config file: /etc/nginx/sites-available/default

# Redirect everything to HTTPS and all subdomains to example.com
server {
	listen 80;
	example.com *.example.com;
	rewrite ^ https://example.com$request_uri? permanent;
}

# HTTPS server
server {
	listen 443;
	server_name example.com;

	# play public assets
	root /var/example/target/scala-2.10/classes/public;

	# SSL configuration
	ssl on;
	ssl_certificate /var/example/target/deployment/example_com.pem;
	ssl_certificate_key /var/example/target/deployment/example_com.key;

	ssl_session_timeout 5m;

	ssl_protocols SSLv3 TLSv1;
	ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP;
	ssl_prefer_server_ciphers on;

	# static content (with client-side caching enabled)
	location /assets/ {
		access_log  off;
		log_not_found off;
		add_header  Pragma "public";
		add_header  Cache-Control "public";
		expires     30d;
		alias /var/example/target/scala-2.10/classes/public/;
	}

	# serve assets or request page from proxy (if asset not found)
	location / {
  		try_files $uri @proxy;
	}

	# the play web server
	location @proxy {
		proxy_pass  http://localhost:9000;
		proxy_redirect off;
		proxy_buffering off;

		#send protocol info to play server
		proxy_set_header        Host               $host;
		proxy_set_header        X-Real-IP          $remote_addr;
		proxy_set_header        X-Forwarded-Proto  https;
		proxy_set_header        X-Forwarded-Ssl    on;
		proxy_set_header        X-Forwarded-For    $proxy_add_x_forwarded_for;
	}
}

Checking for SSL in your play app

Note that in this example configuration, ALL traffic will use HTTPS. If you want to use both HTTP and HTTPS, you’ll have to remove the redirects from http to https. You can check in your play app which protocol the request uses:

def someAction = Action {
	implicit request => {
		val isSecure = request.headers.get("X-Forwarded-Proto").exists(_ == "https")
	}
}
This entry was posted in Play framework and tagged , , , , , . Bookmark the permalink.
  • Djon Mayer

    Hi. Which version of nginx are you using?

    • //blog.papauschek.com/ Christian Papauschek

      Why? We’re using one that comes with Ubuntu, not sure which version exactly.

      • Djon Mayer

        Documentation of PlayFramework says to HTTPServer next: “Note Make shure you are using version > 1.2 of Nginx otherwise chunked responses won’t work properly.”. I use Ubuntu Server 12.04 and Nginx is in Version 1.1.19 there.

        • //blog.papauschek.com/ Christian Papauschek

          We didnt run into issues with this one, probably because we
          have proxy_buffering on;

          This is done in order to use nginx’s caching capabilities in production (maybe ill write more about this in another post)

  • AGhost_7

    In newer versions of play, I think that the path to compiled static assets is:

    /var/example/target/web/public/main/

    • //blog.papauschek.com/ Christian Papauschek

      thanks, I’ll mention it in the post

  • https://www.facebook.com/app_scoped_user_id/1560758005/ Cyril Franceschini

    Nice article thanks!
    I have a question: How do you make absoluteURL() work in your reverse routing as it doesn’t return HTTPS URLs in this case? Thanks.