A plug to force SSL connections and enable HSTS.

If the scheme of a request is https, it’ll add a strict-transport-security header to enable HTTP Strict Transport Security by default.

Otherwise, the request will be redirected to a corresponding location with the https scheme by setting the location header of the response. The status code will be 301 if the method of conn is GET or HEAD, or 307 in other situations.

Besides being a Plug, this module also provides conveniences for configuring SSL. See configure/1.


If your Plug application is behind a proxy that handles HTTPS, you will need to tell Plug to parse the proper protocol from the x-forwarded-proto header. This can be done using the :rewrite_on option:

plug Plug.SSL, rewrite_on: [:x_forwarded_proto]

The command above will effectively change the value of conn.scheme by the one sent in x-forwarded-proto.

Since rewriting the scheme based on x-forwarded-proto can open up security vulnerabilities, only provide the option above if:

  • your app is behind a proxy
  • your proxy strips x-forwarded-proto headers from all incoming requests
  • your proxy sets the x-forwarded-proto and sends it to Plug

Plug Options

  • :rewrite_on - rewrites the scheme to https based on the given headers
  • :hsts - a boolean on enabling HSTS or not, defaults to true
  • :expires - seconds to expires for HSTS, defaults to 7884000 (three months)
  • :preload - a boolean to request inclusion on the HSTS preload list (for full set of required flags, see: Chromium HSTS submission site), defaults to false
  • :subdomains - a boolean on including subdomains or not in HSTS, defaults to false
  • :exclude - exclude the given hosts from redirecting to the https scheme. Defaults to ["localhost"]
  • :host - a new host to redirect to if the request’s scheme is http, defaults to conn.host. It may be set to a binary or a tuple {module, function, args} that will be invoked on demand
  • :log - The log level at which this plug should log its request info. Default is :info. Can be false to disable logging.


It is not possible to directly configure the port in Plug.SSL because HSTS expects the port to be 443 for SSL. If you are not using HSTS and wants to redirect to HTTPS on another port, you can sneak it alongside the host, for example: host: "example.com:443".



call(conn, arg)

Plug pipeline callback


Configures and validates the options given to the :ssl application


Plug initialization callback


call(conn, arg)

Plug pipeline callback.


configure(Keyword.t()) :: {:ok, Keyword.t()} | {:error, String.t()}

Configures and validates the options given to the :ssl application.

This function is often called internally by adapters, such as Cowboy, to validate and set reasonable defaults for SSL handling. Therefore Plug users are not expected to invoke it directly, rather you pass the relevant SSL options to your adapter which then invokes this.

For instance, here is how you would pass the SSL options to the Cowboy adapter:

Plug.Cowboy.https MyPlug, [],
  port: 443,
  password: "SECRET",
  otp_app: :my_app,
  cipher_suite: :strong,
  keyfile: "priv/ssl/key.pem",
  certfile: "priv/ssl/cert.pem",
  dhfile: "priv/ssl/dhparam.pem"

or using the new child spec API:

{Plug.Cowboy, scheme: :https, plug: MyPlug, options: [
   port: 443,
   password: "SECRET",
   otp_app: :my_app,
   cipher_suite: :strong,
   keyfile: "priv/ssl/key.pem",
   certfile: "priv/ssl/cert.pem",
   dhfile: "priv/ssl/dhparam.pem"


This function accepts and validates all options defined in the ssl erlang module. With the following additions:

  • The :cipher_suite option provides :strong and :compatible options for setting up better cipher and version defaults according to the OWASP recommendations. See the “Cipher Suites” section below

  • The certificate files, like keyfile, certfile, cacertfile, dhfile can be given as a relative path. For such, the :otp_app option must also be given and certificates will be looked from the priv directory of the given application

  • In order to provide better security, this function also enables :reuse_sessions and :secure_renegotiate by default, to instruct clients to reuse sessions and enforce secure renegotiation according to RFC 5746 respectively

Cipher Suites

To simplify configuration of TLS defaults Plug provides two preconfifured options: cipher_suite: :strong and cipher_suite: :compatible. The Ciphers chosen and related configuration come from the OWASP recommendations found here: https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet

We’ve made two modifications to the suggested config from the OWASP recommendations. First we include ECDSA certificates which are excluded from their configuration. Second we have changed the order of the ciphers to deprioritize DHE because of performance implications noted within the OWASP post itself. As the article notes “…the TLS handshake with DHE hinders the CPU about 2.4 times more than ECDHE”.

The Strong cipher suite only supports tlsv1.2. Ciphers were based on the OWASP Group A+ and includes support for RSA or ECDSA certificates. The intention of this configuration is to provide as secure as possible defaults knowing that it will not be fully compatible with older browsers and operating systems.

The Compatible cipher suite supports tlsv1, tlsv1.1 and tlsv1.2. Ciphers were based on the OWASP Group B and includes support for RSA or ECDSA certificates. The intention of this configuration is to provide as secure as possible defaults that still maintain support for older browsers and Android versions 4.3 and earlier

For both suites we’ve specified ceritifcate curves secp256r1, ecp384r1 and secp521r1. Since OWASP doesn’t perscribe curves we’ve based the selection on the following Mozilla recommendations: https://wiki.mozilla.org/Security/Server_Side_TLS#Cipher_names_correspondence_table

In addition to selecting a group of ciphers, selecting a cipher suite will also disable client renegotiation and force the client to honor the server specified cipher order.

Any of those choices can be disabled on a per choice basis by specifying the equivalent SSL option alongside the cipher suite.

The cipher suites were last updated on 2018-JUN-14.

Manual Cipher Configuration

Should you choose to configure your own ciphers you cannot use the :cipher_suite option as setting a cipher suite overrides your cipher selections.

Instead, you can see the valid options for ciphers in the Erlang SSL documentation: http://erlang.org/doc/man/ssl.html

Please note that specifying a cipher as a binary string is not valid and would silently fail in the past. This was problematic because the result would be for Erlang to use the default list of ciphers. To prevent this Plug will now throw an error to ensure you’re aware of this.

Diffie Hellman parameters

It is recommended to generate a custom set of Diffie Hellman parameters, to be used for the DHE key exchange. Use the following OpenSSL CLI command to create a ‘dhparam.pem’ file:

openssl dhparam -out dhparam.pem 4096

On a slow machine (e.g. a cheap VPS) this may take several hours. You may want to run the command on a strong machine and copy the file over: the file does not need to be kept secret.

Add the resulting file to your application’s priv directory and pass the path using the :dhfile key. It is best practice to rotate the file periodically.

If no custom parameters are specified, Erlang’s ssl uses its built-in defaults. Since OTP 19 this has been the 2048-bit ‘group 14’ from RFC 3526.


Plug initialization callback.

© 2013 Plataformatec
Licensed under the Apache License, Version 2.0.