TLS for servers

In this section, we give a very short and quick description on how to enable SSL/TLS for a server (listening) connection - for example, HTTPS server.

  • Obtain SSL certificate file and private key file (or create your own self-signed certificate, see below)

  • In the event handler function, call mg_tls_init() on MG_EV_ACCEPT event:

    static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
      if (ev == MG_EV_ACCEPT) {
        struct mg_tls_opts opts = {
          .cert = "cert.pem",    // Certificate file
          .certkey = "key.pem",  // Private key file
        mg_tls_init(c, &opts);
  • Rebuild the application, see build instructions below

You can see example code in

TLS for clients

In order to TLS-enable client connections,

  • Obtain SSL CA (Certificate Authority) file

  • In the event handler function, call mg_tls_init() on MG_EV_CONNECT event:

  • Rebuild the application, see build instructions below

You can see example code in

Certificates overview

TLS provides two major benefits:

  • traffic encryption, which makes it impossible to sniff and look inside the traffic, and
  • authentication, which makes it possible to one side of TLS connection verify the identity of the other side.

Here we're talking about the authentication. Authentication is implemented via certificates. A certificate has two parts - public and private. Talking in practical terms, three files are required to implement TLS authentication:

  • TLS certificate. This is a "public" part. For example, TLS-enabled server sends it to the client during TLS handshake
  • TLS private key. This is a "private" part
  • TLS Certificate Authority (CA) file. It is used for verification of the "public" certificate sent by a server

TLS certificates are obtained from services like Let's Encrypt. The other possibility is self-signed certificates, which are mainly used for development.

Self-signed certificates

It is possible to generate certificate files. Note that servers using those files will not be trusted by the browsers, because browsers use a pre-installed CA files and know nothing about your generated certificates.

A command below uses openssl command line tool to generate a self-signed server certificate and a key file:

$ openssl req  -nodes -new -x509  -keyout key.pem -out cert.pem

Two way TLS

Normally, when a client makes a connection to a TLS-enabled server, a server sends its certificate to the client and client verifies it using its own CA (Certificate Authority) file. This way a client authenticates a server.

In the most common situation, a client verifies a server, but a server does not verify a client. For example, browsers (clients) use a big CA file (or many CA files) to verify HTTPS servers.

Clients can also provide certificates during TLS handshake, and a server can verify it using a CA file. When both client and server use certificates, and verify the other side using CA file, is called two-way TLS. In order to implement two-way TLS, both client and server must have cert, certkey and ca specified in the mg_tls_opts:

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_CONNECT) {
    struct mg_str host = mg_url_host(s_url);
    if (mg_url_is_ssl(s_url)) {
      struct mg_tls_opts opts = {
        .ca = "ca.pem",         // CA file
        .cert = "cert.pem",     // Certificate file
        .certkey = "key.pem",   // Private key file
        .srvname = host,        // Only for client connections
      mg_tls_init(c, &opts);

Two way TLS certificates

It is possible to generate self-signed ca.pem, and cert + key pairs for for both client and server for two-way (mutual) authentication. There how it is done using openssl command line tool:

# Common parameters
$ SUBJ="/C=IE/ST=Dublin/L=Docks/O=MyCompany/CN=howdy"

# Generate CA
$ openssl genrsa -out ca.key 2048
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
  -subj /C=IE/ST=Dublin/L=Docks/O=mos/CN=me 

# Generate client cert
$ openssl genrsa -out client.key 2048
$ openssl req -new -key client.key -out client.csr -subj $SUBJ
$ openssl x509 -req -days 365 -in client.csr -CA ca.crt \
  -CAkey ca.key -set_serial 01 -out client.crt

# Generate server cert
$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -out server.csr -subj $SUBJ
$ openssl x509 -req -days 365 -in server.csr -CA ca.crt \
  -CAkey ca.key -set_serial 01 -out server.crt

How to build

Mongoose has to be built with MG_ENABLE_MBEDTLS=1 or MG_ENABLE_OPENSSL=1, depending on what TLS library you use.

Example build on Linux using mbedTLS:

$ cc main.c mongoose.c -DMG_ENABLE_MBEDTLS=1 -lmbedtls -lmbedcrypto -lmbedx509

Example build on Linux using OpenSSL:

$ cc main.c mongoose.c -DMG_ENABLE_OPENSSL=1 -lssl -lcrypto

Most Makefiles in Mongoose examples take care of this. If you see a compiler error message like "Cannot find .... file", your system needs extra paths for includes and/or libs, and/or link options; usually the former. This means adding -I path/to/include to tell where installed TLS headers are, and/or -L path/to/lib to tell where installed TLS shared libs are.

These paths and link options are usually determined in GNU/Linux-like systems using pkg-config. Sometimes the package uses the standard paths and no extra paths are needed; others they are installed in a different path to avoid conflicts and flags are required:

$ pkg-config --cflags --libs openssl
-lssl -lcrypto
$ pkg-config --cflags --libs openssl11
-I/usr/include/openssl11  -L/usr/lib64/openssl11 -lssl -lcrypto
$ pkg-config --cflags --libs-only-L openssl11
-I/usr/include/openssl11  -L/usr/lib64/openssl11
$ pkg-config --libs-only-l openssl11
-lssl -lcrypto

This requires that the relevant packages have installed there packagename.pc files in the default path where pkg-config will search for them; otherwise you have to set the PKG_CONFIG_PATH variable to where they reside.

Most Makefiles in Mongoose examples accept an EXTRA_CFLAGS argument where you can pass extra options, like:

$ make all SSL=MBEDTLS EXTRA_CFLAGS="-I /path/toinclude -L /path/to/lib"

The link options are mostly the same among systems and usually there is no need to add to them.

Check your system for relevant paths and compiler flags