Server-Side HTTPS Support

Akka HTTP supports TLS encryption on the server-side as well as on the client-side.

The central vehicle for configuring encryption is the HttpsConnectionContext, which can be created using the static method ConnectionContext.https which is defined like this:

Scala
// ConnectionContext
def https(
  sslContext:          SSLContext,
  sslConfig:           Option[AkkaSSLConfig]         = None,
  enabledCipherSuites: Option[immutable.Seq[String]] = None,
  enabledProtocols:    Option[immutable.Seq[String]] = None,
  clientAuth:          Option[TLSClientAuth]         = None,
  sslParameters:       Option[SSLParameters]         = None) =
  new HttpsConnectionContext(sslContext, sslConfig, enabledCipherSuites, enabledProtocols, clientAuth, sslParameters)
Java
// ConnectionContext
/** Used to serve HTTPS traffic. */
def https(sslContext: SSLContext): HttpsConnectionContext =
  scaladsl.ConnectionContext.https(sslContext)

/** Used to serve HTTPS traffic. */
def https(
  sslContext:          SSLContext,
  sslConfig:           Optional[AkkaSSLConfig],
  enabledCipherSuites: Optional[JCollection[String]],
  enabledProtocols:    Optional[JCollection[String]],
  clientAuth:          Optional[TLSClientAuth],
  sslParameters:       Optional[SSLParameters]) =
  scaladsl.ConnectionContext.https(
    sslContext,
    OptionConverters.toScala(sslConfig),
    OptionConverters.toScala(enabledCipherSuites).map(Util.immutableSeq(_)),
    OptionConverters.toScala(enabledProtocols).map(Util.immutableSeq(_)),
    OptionConverters.toScala(clientAuth),
    OptionConverters.toScala(sslParameters))

On the server-side the bind, and bindAndHandleXXX methods of the akka.http.scaladsl.Httpakka.http.javadsl.Http extension define an optional httpsContext parameter, which can receive the HTTPS configuration in the form of an HttpsContext instance. If defined encryption is enabled on all accepted connections. Otherwise it is disabled (which is the default).

For detailed documentation for client-side HTTPS support refer to Client-Side HTTPS Support.

SSL-Config

Akka HTTP heavily relies on, and delegates most configuration of any SSL/TLS related options to Lightbend SSL-Config, which is a library specialized in providing an secure-by-default SSLContext and related options.

Please refer to the Lightbend SSL-Config documentation for detailed documentation of all available settings.

SSL Config settings used by Akka HTTP (as well as Streaming TCP) are located under the akka.ssl-config namespace.

In order to use SSL-Config in Akka so it logs to the right ActorSystem-wise logger etc., the AkkaSSLConfig extension is provided. Obtaining it is as simple as:

Scala
implicit val system = ActorSystem()
val sslConfig = AkkaSSLConfig()
Java
final ActorSystem system = ActorSystem.create();

final AkkaSSLConfig sslConfig = AkkaSSLConfig.get(system);

While typical usage, for example for configuring http client settings would be applied globally by configuring ssl-config in application.conf, it’s possible to obtain the extension and copy it while modifying any configuration that you might need to change and then use that specific AkkaSSLConfig instance while establishing connections be it client or server-side.

Obtaining SSL/TLS Certificates

In order to run an HTTPS server a certificate has to be provided, which usually is either obtained from a signing authority or created by yourself for local or staging environment purposes.

Signing authorities often provide instructions on how to create a Java keystore (typically with reference to Tomcat configuration). If you want to generate your own certificates, the official Oracle documentation on how to generate keystores using the JDK keytool utility can be found here.

SSL-Config provides a more targeted guide on generating certificates, so we recommend you start with the guide titled Generating X.509 Certificates.

Using HTTPS

Once you have obtained the server certificate, using it is as simple as preparing an HttpsConnectionContext and either setting it as the default one to be used by all servers started by the given Http extension or passing it in explicitly when binding the server.

The below example shows how setting up HTTPS works. First, you create and configure an instance of HttpsConnectionContext :

Scala
import java.io.InputStream
import java.security.{ SecureRandom, KeyStore }
import javax.net.ssl.{ SSLContext, TrustManagerFactory, KeyManagerFactory }

import akka.actor.ActorSystem
import akka.http.scaladsl.server.{ Route, Directives }
import akka.http.scaladsl.{ ConnectionContext, HttpsConnectionContext, Http }
import akka.stream.ActorMaterializer
import com.typesafe.sslconfig.akka.AkkaSSLConfig
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
implicit val dispatcher = system.dispatcher

// Manual HTTPS configuration

val password: Array[Char] = "change me".toCharArray // do not store passwords in code, read them from somewhere safe!

val ks: KeyStore = KeyStore.getInstance("PKCS12")
val keystore: InputStream = getClass.getClassLoader.getResourceAsStream("server.p12")

require(keystore != null, "Keystore required!")
ks.load(keystore, password)

val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(ks, password)

val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(ks)

val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
val https: HttpsConnectionContext = ConnectionContext.https(sslContext)
Java
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;

import akka.http.javadsl.HttpsConnectionContext;

  // ** CONFIGURING ADDITIONAL SETTINGS ** //

  public static HttpsConnectionContext useHttps(ActorSystem system) {
      HttpsConnectionContext https = null;
      try {
        // initialise the keystore
        // !!! never put passwords into code !!!
        final char[] password = new char[]{'a', 'b', 'c', 'd', 'e', 'f'};

        final KeyStore ks = KeyStore.getInstance("PKCS12");
        final InputStream keystore = SimpleServerApp.class.getClassLoader().getResourceAsStream("httpsDemoKeys/keys/server.p12");
        if (keystore == null) {
          throw new RuntimeException("Keystore required!");
        }
        ks.load(keystore, password);

        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(ks, password);

        final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(ks);

        final SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

        https = ConnectionContext.https(sslContext);

      } catch (NoSuchAlgorithmException | KeyManagementException e) {
        system.log().error("Exception while configuring HTTPS.", e);
      } catch (CertificateException | KeyStoreException | UnrecoverableKeyException | IOException e) {
        system.log().error("Exception while ", e);
      }

      return https;
  }

Once you configured the HTTPS context, you can set it as default: Then pass it to the akka.http.javadsl.Http class’s setDefaultServerHttpContext method, like in the below main method.

Scala
// sets default context to HTTPS – all Http() bound servers for this ActorSystem will use HTTPS from now on
Http().setDefaultServerHttpContext(https)
Http().bindAndHandle(routes, "127.0.0.1", 9090, connectionContext = https)
Java
public Route multiply(int x, int y) {
  int result = x * y;
  return complete(String.format("%d * %d = %d", x, y, result));
}

public CompletionStage<Route> multiplyAsync(Executor ctx, int x, int y) {
  return CompletableFuture.supplyAsync(() -> multiply(x, y), ctx);
}

public Route createRoute() {
  Route addHandler = parameter(StringUnmarshallers.INTEGER, "x", x ->
    parameter(StringUnmarshallers.INTEGER, "y", y -> {
      int result = x + y;
      return complete(String.format("%d + %d = %d", x, y, result));
    })
  );

  BiFunction<Integer, Integer, Route> subtractHandler = (x, y) -> {
    int result = x - y;
    return complete(String.format("%d - %d = %d", x, y, result));
  };

  return
    route(
      // matches the empty path
      pathSingleSlash(() ->
        getFromResource("web/calculator.html")
      ),
      // matches paths like this: /add?x=42&y=23
      path("add", () -> addHandler),
      path("subtract", () ->
        parameter(StringUnmarshallers.INTEGER, "x", x ->
          parameter(StringUnmarshallers.INTEGER, "y", y ->
            subtractHandler.apply(x, y)
          )
        )
      ),
      // matches paths like this: /multiply/{x}/{y}
      path(PathMatchers.segment("multiply").slash(integerSegment()).slash(integerSegment()),
        this::multiply
      ),
      path(PathMatchers.segment("multiplyAsync").slash(integerSegment()).slash(integerSegment()), (x, y) ->
        extractExecutionContext(ctx ->
          onSuccess(() -> multiplyAsync(ctx, x, y), Function.identity())
        )
      ),
      post(() ->
        path("hello", () ->
          entity(entityToString(), body ->
            complete("Hello " + body + "!")
          )
        )
      )
    );
}

// ** STARTING THE SERVER ** //

public static void main(String[] args) throws IOException {
  final ActorSystem system = ActorSystem.create("SimpleServerApp");
  final ActorMaterializer materializer = ActorMaterializer.create(system);
  final Http http = Http.get(system);

  boolean useHttps = false; // pick value from anywhere
  if ( useHttps ) {
    HttpsConnectionContext https = useHttps(system);
    http.setDefaultServerHttpContext(https);
  }

  final SimpleServerApp app = new SimpleServerApp();
  final Flow<HttpRequest, HttpResponse, NotUsed> flow = app.createRoute().flow(system, materializer);

  Http.get(system).bindAndHandle(flow, ConnectHttp.toHost("localhost", 8080), materializer);

  System.out.println("Type RETURN to exit");
  System.in.read();
  system.terminate();
}

It is also possible to pass in the context to specific bind... (or client) calls, like displayed below:

Http().bind("127.0.0.1", connectionContext = https)

// or using the high level routing DSL:
val routes: Route = get { complete("Hello world!") }
Http().bindAndHandle(routes, "127.0.0.1", 8080, connectionContext = https)

Running both HTTP and HTTPS

If you want to run HTTP and HTTPS servers in a single application, you can call bind... methods twice, one for HTTPS, and the other for HTTP.

When configuring HTTPS, you can do it up like explained in the above Using HTTPS section,

Scala
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
implicit val dispatcher = system.dispatcher

// Manual HTTPS configuration

val password: Array[Char] = "change me".toCharArray // do not store passwords in code, read them from somewhere safe!

val ks: KeyStore = KeyStore.getInstance("PKCS12")
val keystore: InputStream = getClass.getClassLoader.getResourceAsStream("server.p12")

require(keystore != null, "Keystore required!")
ks.load(keystore, password)

val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(ks, password)

val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(ks)

val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
val https: HttpsConnectionContext = ConnectionContext.https(sslContext)
Java
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;

import akka.http.javadsl.HttpsConnectionContext;

  // ** CONFIGURING ADDITIONAL SETTINGS ** //

  public static HttpsConnectionContext useHttps(ActorSystem system) {
      HttpsConnectionContext https = null;
      try {
        // initialise the keystore
        // !!! never put passwords into code !!!
        final char[] password = new char[]{'a', 'b', 'c', 'd', 'e', 'f'};

        final KeyStore ks = KeyStore.getInstance("PKCS12");
        final InputStream keystore = SimpleServerApp.class.getClassLoader().getResourceAsStream("httpsDemoKeys/keys/server.p12");
        if (keystore == null) {
          throw new RuntimeException("Keystore required!");
        }
        ks.load(keystore, password);

        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(ks, password);

        final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(ks);

        final SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());

        https = ConnectionContext.https(sslContext);

      } catch (NoSuchAlgorithmException | KeyManagementException e) {
        system.log().error("Exception while configuring HTTPS.", e);
      } catch (CertificateException | KeyStoreException | UnrecoverableKeyException | IOException e) {
        system.log().error("Exception while ", e);
      }

      return https;
  }

or via SSL-Config (not explained here though).

Then, call bind... methods twice like below. The passed https context is from the above code snippet. SimpleServerApp.useHttps(system) is calling the above defined public static HttpsConnectionContext useHttps(ActorSystem system) method.

Scala
// you can run both HTTP and HTTPS in the same application as follows:
val commonRoutes: Route = get { complete("Hello world!") }
Http().bindAndHandle(commonRoutes, "127.0.0.1", 443, connectionContext = https)
Http().bindAndHandle(commonRoutes, "127.0.0.1", 80)
Java
final Http http = Http.get(system);
//Run HTTP server firstly
http.bindAndHandle(flow, ConnectHttp.toHost("localhost", 80), materializer);

//get configured HTTPS context
HttpsConnectionContext https = SimpleServerApp.useHttps(system);

// sets default context to HTTPS – all Http() bound servers for this ActorSystem will use HTTPS from now on
http.setDefaultServerHttpContext(https);

//Then run HTTPS server
http.bindAndHandle(flow, ConnectHttp.toHost("localhost", 443), materializer);

Mutual authentication

To require clients to authenticate themselves when connecting, pass in Some(TLSClientAuth.Need)Optional.of(TLSClientAuth.need) as the clientAuth parameter of the HttpsConnectionContextHttpsConnectionContext and make sure the truststore is populated accordingly. For further (custom) certificate checks you can use the Tls-Session-InfoTlsSessionInfo synthetic header.

At this point dynamic renegotiation of the certificates to be used is not implemented. For details see issue #18351 and some preliminary work in PR #19787.

Further reading

The topic of properly configuring HTTPS for your web server is an always changing one, thus we recommend staying up to date with various security breach news and of course keep your JVM at the latest version possible, as the default settings are often updated by Oracle in reaction to various security updates and known issues.

We also recommend having a look at the Play documentation about securing your app, as well as the techniques described in the Play documentation about setting up a reverse proxy to terminate TLS in front of your application instead of terminating TLS inside the JVM, and therefore Akka HTTP, itself.

Other excellent articles on the subject:

The source code for this page can be found here.