Search documentation of Phoenix

# Using SSL

View Source

To prepare an application to serve requests over SSL, we need to add a little bit of configuration and two environment variables. In order for SSL to actually work, we'll need a key file and certificate file from a certificate authority. The environment variables that we'll need are paths to those two files.

The configuration consists of a new `https:` key for our endpoint whose value is a keyword list of port, path to the key file, and path to the cert (PEM) file. If we add the `otp_app:` key whose value is the name of our application, Plug will begin to look for them at the root of our application. We can then put those files in our `priv` directory and set the paths to `priv/our_keyfile.key` and `priv/our_cert.crt`.

Here's an example configuration from `config/runtime.exs`.

    import Config

    config :hello, HelloWeb.Endpoint,
      http: [port: String.to_integer(System.get_env("PORT"))],
      url: [host: "example.com"],
      cache_static_manifest: "priv/static/cache_manifest.json",
      https: [
        port: 443,
        cipher_suite: :strong,
        otp_app: :hello,
        keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
        certfile: System.get_env("SOME_APP_SSL_CERT_PATH"),
        # OPTIONAL Key for intermediate certificates:
        cacertfile: System.get_env("INTERMEDIATE_CERTFILE_PATH")
      ]

Without the `otp_app:` key, we need to provide absolute paths to the files wherever they are on the filesystem in order for Plug to find them.

    Path.expand("../../../some/path/to/ssl/key.pem", __DIR__)

The options under the `https:` key are passed to the Plug adapter, typically `Bandit`, which in turn uses `Plug.SSL` to select the TLS socket options. Please refer to the documentation for Plug.SSL.configure/1 for more information on the available options and their defaults. The Plug HTTPS Guide and the Erlang/OTP ssl documentation also provide valuable information.

## SSL in Development

If you would like to use HTTPS in development, a self-signed certificate can be generated by running: `mix phx.gen.cert`. This requires Erlang/OTP 20 or later.

With your self-signed certificate, your development configuration in `config/dev.exs` can be updated to run an HTTPS endpoint:

    config :my_app, MyAppWeb.Endpoint,
      ...
      https: [
        port: 4001,
        cipher_suite: :strong,
        keyfile: "priv/cert/selfsigned_key.pem",
        certfile: "priv/cert/selfsigned.pem"
      ]

This can replace your `http` configuration, or you can run HTTP and HTTPS servers on different ports.

## Force SSL

In many cases, you'll want to force all incoming requests to use SSL by redirecting HTTP to HTTPS. This can be accomplished by setting the `:force_ssl` option in your endpoint configuration. It expects a list of options which are forwarded to `Plug.SSL`. By default, it sets the "strict-transport-security" header in HTTPS requests, forcing browsers to always use HTTPS. For example:

    config :my_app, MyAppWeb.Endpoint,
      force_ssl: [rewrite_on: [:x_forwarded_proto]]

If an unsafe (HTTP) request is sent, the above will redirect to the HTTPS version using the `:host` specified in the `:url` configuration. The `rewrite_on:` key specifies the HTTP header used by a reverse proxy or load balancer in front of the application to indicate whether the request was received over HTTP or HTTPS.

To dynamically redirect to the `host` of the current request, set `:host` in the `:force_ssl` configuration to `nil`. In such cases, you must also set `x_forwarded_host` and `x_forwarded_port` if running behind a proxy:

    config :my_app, MyAppWeb.Endpoint,
      force_ssl: [rewrite_on: [:x_forwarded_proto, :x_forwarded_host, :x_forwarded_port], host: nil]

Furthermore, keep in mind `force_ssl` will redirect all requests, except the ones coming from localhost. If your application is doing probeness checks using another origin, such as "127.0.0.1" or an internal IP address, you may need to explicitly exclude them from `force_ssl`:

    config :my_app, MyAppWeb.Endpoint,
      force_ssl: [rewrite_on: [:x_forwarded_proto], exclude: ["localhost", "127.0.0.1"]]

It is important to note that `force_ssl:` is a compile time config, so it normally is set in `prod.exs`, it will not work when set from `runtime.exs`.

For more information on the implications of offloading TLS to an external element, in particular relating to secure cookies, refer to the Plug HTTPS Guide. Keep in mind that the options passed to `Plug.SSL` in that document should be set using the `force_ssl:` endpoint option in a Phoenix application.

## HSTS

HSTS, short for 'HTTP Strict-Transport-Security', is a mechanism that allows websites to declare themselves as accessible exclusively through a secure connection (HTTPS). It was introduced to prevent man-in-the-middle attacks that strip SSL/TLS encryption. HSTS causes web browsers to redirect from HTTP to HTTPS and to refuse to connect unless the connection uses SSL/TLS.

With `force_ssl: [hsts: true]` set, the `Strict-Transport-Security` header is added with a max-age that defines the duration for which the policy is valid. Modern web browsers will respond to this by redirecting from HTTP to HTTPS, among other consequences. RFC6797, which defines HSTS, also specifies that the browser should keep track of a host's policy and apply it until it expires. It further specifies that traffic on any port other than 80 is assumed to be encrypted as per the policy.

While HSTS is recommended in production, it can lead to unexpected behavior when accessing applications on localhost. For instance, accessing an application with HSTS enabled at `https://localhost:4000` leads to a situation where all subsequent traffic from localhost, except for port 80, is expected to be encrypted. This can disrupt traffic to other local servers or proxies running on your computer that are unrelated to your Phoenix application and may not support encrypted traffic.

If you inadvertently enable HSTS for localhost, you may need to reset your browser's cache before it will accept HTTP traffic from localhost again.

For Chrome:

1. Open the Developer Tools Panel.
2. Click and hold the reload icon next to the address bar to reveal a dropdown menu.
3. Select "Empty Cache and Hard Reload".

For Safari:

1. Clear your browser cache.
2. Remove the entry from `~/Library/Cookies/HSTS.plist` or delete the file entirely.
3. Restart Safari.

For other browsers, please consult the documentation for HSTS.

Alternatively, setting the `:expires` option on `force_ssl` to `0` should expire the entry and disable HSTS.

For more information on HSTS options, see Plug.SSL.

← Previous Page Swapping Databases
Next Page → Writing a Channels Client