Skip to main content

Deploy to production

This guide explains how to set up and run Ory Hydra in an exemplary production environment. It uses Postgres as database, Nginx as reverse proxy, and Digital Ocean as cloud provider. You can use another relational database, a different reverse proxy, deploy on any other cloud host, and spin up a custom user interface in your favorite language - this is just an example!

Create a Droplet

Spin up a Droplet (virtual machine) with the following configuration:

  • OS: Ubuntu 20.04
  • Plan: Basic
  • CPU options: Regular with SSD
  • RAM: 1Gb
  • SSD: 25Gb
  • VPC network: default
  • Authentication: SSH Keys. Don't forget to add your SSH key.
  • Region: Choose your region
note

This example shows a basic configuration of Ory Hydra on a single virtual machine (VM). The configuration of a VM may vary depending on the scale of your application.

Wait for the Droplet to start up and copy the IP address.

This guide uses oauth2.example.com as a hostname to run Ory Hydra. Replace it with <something>.<your-domain> for your purposes. At your hosting provider, configure DNS, create an A type record, and point it to the Droplet IP.

Install dependencies

Connect to your Droplet via SSH or use the Droplet Console.

First, upgrade all packages on your Droplet.

apt-get update && apt-get upgrade

Since the default version of Node.js is outdated we need to install a newer version. You can use the following script to install Node.js 16 on a Ubuntu system.

curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh
bash /tmp/nodesource_setup.sh
apt-get install nodejs npm jq unzip
# Install Node.js 16.x and other dependencies

Confirm the correct version of Node.js is installed:

node -v
v16.14.2

Install PostgreSQL

  1. Install PostgreSQL by running the following command:

    sudo apt install postgresql postgresql-contrib
    sudo -i -u postgres
  2. Create the database:

    createdb hydra
  3. Change the default password encryption to a stronger one as recommended by PostgreSQL:

    psql
    # Postgres command line
    ALTER SYSTEM SET password_encryption = 'scram-sha-256';
    # Change the default password encryption to stronger one
    SELECT pg_reload_conf();
    # Reload configuration
  4. Create a Postgres user for Hydra:

    CREATE USER hydra PASSWORD '<YOUR_PASSWORD_HERE>';
  5. Edit the /etc/postgresql/12/main/pg_hba.conf file and add the following to enable scram-sha-256 encryption:

    host    all             all             127.0.0.1/32            scram-sha-256
  6. Check your credentials against Postgres:

    psql -U hydra -W -h 127.0.0.1
  7. Use the password for the Ory Hydra user we created before to log into Postgres. If everything works correctly you should see a prompt similar to this:

    Password:
    psql (12.9 (Ubuntu 12.9-0ubuntu0.20.04.1))
    SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
    Type "help" for help.

    hydra=>

Congratulations, you have installed and configured the PostgreSQL database. Next, you will set up Ory Hydra.

Install Ory Hydra

  1. Create a new user with prohibited login:

    useradd -s /bin/false -m -d /opt/hydra hydra
  2. Create folders to hold the installation data and configuration files:

    mkdir /opt/hydra/{bin,config}
  3. Install Ory Hydra:

    cd /opt/hydra/bin
    # Download a new version of Ory Hydra
    wget https://github.com/ory/hydra/releases/download/<version-you-want>/hydra_<version-you-want>-linux_64bit.tar.gz
    # Extract contents
    tar xfvz hydra_<version-you-want>-linux_64bit.tar.gz
    # Remove redundant files
    rm *md
    rm LICENSE
  4. Download the Quickstart configuration files

    cd ../config
    wget https://raw.githubusercontent.com/ory/hydra/<version-you-want>/contrib/quickstart/5-min/hydra.yml
  5. Add configuration for Ory Hydra in hydra.yml to use the Postgres database. Open hydra.yml and change the DSN configuration. Ory Hydra is storing data in an SQLite database in the default quickstart configuration. Change the DSN key to use the Postgres database you configured before and add the issuer URL. This URL will be used as issuer in access and ID tokens.

    dsn: postgres://hydra:[email protected]:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
    urls:
    self:
    issuer: https://oauth2.example.com
  6. Apply migrations:

    /opt/hydra/bin/hydra -c /opt/hydra/config/hydra.yml migrate sql -y postgres://hydra:[email protected]:5432/hydra?sslmode=disable
  7. Test your setup using the serve command:

    /opt/hydra/bin/hydra -c /opt/hydra/config/hydra.yml serve all

You should see something like this once the service has been started:

WARN[2022-04-15T15:24:29Z] JSON Web Key Set "hydra.openid.id-token" does not exist yet, generating new key pair...  audience=application service_name=Ory Hydra service_version=v1.11.7
WARN[2022-04-15T15:24:34Z] JSON Web Key Set "hydra.jwt.access-token" does not exist yet, generating new key pair... audience=application service_name=Ory Hydra service_version=v1.11.7
Thank you for using Ory Hydra v1.11.7!

Take security seriously and subscribe to the Ory Security Newsletter. Stay on top of new patches and security insights.

>> Subscribe now: http://eepurl.com/di390P <<
INFO[2022-04-15T15:24:37Z] Software quality assurance features are enabled. Learn more at: https://www.ory.sh/docs/ecosystem/sqa audience=application service_name=Ory Hydra service_version=v1.11.7
WARN[2022-04-15T15:24:37Z] JSON Web Key Set "hydra.https-tls" does not exist yet, generating new key pair... audience=application service_name=Ory Hydra service_version=v1.11.7
INFO[2022-04-15T15:24:41Z] Setting up http server on :4444 audience=application service_name=Ory Hydra service_version=v1.11.7
INFO[2022-04-15T15:24:41Z] Setting up http server on :4445 audience=application service_name=Ory Hydra service_version=v1.11.7

Run Ory Hydra using systemd

  1. Change the owner of /opt/hydra directory to the hydra user:

    chown -R hydra /opt/hydra/
  2. Create a /etc/systemd/system/kratos.service file

    cd /etc/systemd/system
    nano hydra.service
  3. Add the following to configure systemd to start Ory Hydra using the serve command from earlier:

    [Unit]
    Description=Hydra Service
    After=network.target
    StartLimitIntervalSec=0

    [Service]
    Type=simple
    Restart=always
    RestartSec=1
    User=hydra
    Environment=SERVE_ADMIN_HOST=127.0.0.1
    Environment=SERVE_PUBLIC_HOST=127.0.0.1
    ExecStart=/opt/hydra/bin/hydra -c /opt/hydra/config/hydra.yml serve all

    [Install]
    WantedBy=multi-user.target

Read more about the administrative and public APIs.

  1. To run Ory Hydra using systemd add the systemd service to startup:

    systemctl enable hydra.service
    Created symlink /etc/systemd/system/multi-user.target.wants/hydra.service → /etc/systemd/system/hydra.service.
  2. Start hydra.service using systemd:

    systemctl start hydra.service
  3. Check running processes with ps ax | grep hydra. If everything worked correctly you should see something like this:

    ps ax | grep hydra
    19191 ? Ssl 0:00 /opt/hydra/bin/hydra -c /opt/hydra/config/hydra.yml serve
    19206 ? Ss 0:00 postgres: 12/main: hydra hydra 127.0.0.1(36094) idle

We have Ory Kratos up and running, now we need to configure a reverse proxy to make the Hydra Admin API inaccessible via the public internet.

Install and configure Nginx

We'll use Nginx as a reverse proxy and load balancer for our service. You can use another reverse proxy and load balancer instead.

  1. Install Nginx (and certbot) by running:

    apt install nginx certbot python3-certbot-nginx
  2. Create a default configuration for the virtual host. To do this create the file accounts.example.com at /etc/nginx/sites-available/ with the following content

    cd /etc/nginx/sites-available/
    nano oauth2.example.com
    server {
    listen 80;
    server_name oauth2.example.com;
    }
  3. Create a symlink to sites-enabled directory:

    ln -s /etc/nginx/sites-available/oauth2.example.com /etc/nginx/sites-enabled/oauth2.example.com
  4. Configure SSL via Certbot. Run the following command and answer the questions to set it up. When prompted choose to redirect HTTP traffic to HTTPS.

    certbot --nginx -d oauth2.example.com

After running Certbot your configuration file will look like this:

cat /etc/nginx/sites-enabled/oauth2.example.com
server {
listen 80;
server_name oauth2.example.com;
if ($host = oauth2.example.com) {
return 301 https://$host$request_uri;
}
}
server {
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/oauth2.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/oauth2.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

Some parts are missing from your configuration at this point, and we need to configure locations and upstream APIs. You can balance network traffic between different instances of Ory Hydra running on the various virtual machines. We need two upstream APIs:

  • public_api to proxy traffic to the Public API of Ory Hydra
  • admin_api to proxy traffic to the Admin API of Ory Hydra

Read more about exposing admin and public API endpoints.

  1. Add the following configuration before the server section to the /etc/nginx/sites-enabled/oauth2.example.com file:

    +upstream public_api {
    + server 127.0.0.1:4444;
    # We can load balance the traffic to support scaling
    + server 127.0.0.1:4444;
    +}
    +upstream admin_api {
    + server 127.0.0.1:4445;
    + server 127.0.0.1:4445;
    +}
    server {
    ...
  2. Add your locations and the /etc/nginx/sites-enabled/oauth2.example.com has the following content:

    ...
    server {
    listen [::]:443 ssl ipv6only=on;
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/oauth2.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/oauth2.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    + location ~ ^/(.well-known|oauth2/auth|oauth2/token|oauth2/revoke|oauth2/fallbacks/consent|oauth2/fallbacks/error|userinfo)/? {
    + proxy_pass http://public_api;
    + proxy_redirect off;
    + proxy_set_header Host $host;
    + proxy_set_header X-Real-IP $remote_addr;
    + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
    +
    + }
    + location ~ ^/(clients|keys|health|metrics|version|oauth2/auth/requests|oauth2/introspect|oauth2/flush)/? {
    + set $allow 0;
    + if ($remote_addr ~* "172.28.0.*") {
    + set $allow 1;
    + }
    + if ($arg_secret = "CHANGE-ME-INSECURE-PASSWORD") {
    + set $allow 1;
    + }
    + if ($allow = 0) {
    + return 403;
    + }
    +
    + rewrite /admin/(.*) /$1 break;
    +
    + proxy_pass http://admin_api;
    + proxy_redirect off;
    + proxy_set_header Host $host;
    + proxy_set_header X-Real-IP $remote_addr;
    + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    + proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
    + }

    }

Let's take a closer look at the configuration. We have two location blocks.

  • The first location block proxies network traffic to the public API of Ory Hydra. It allows to utilize OAuth 2.0 flows and can be exposed to the public internet without additional authentication/authorization/access control checks.
  • The second location block proxies network traffic to the admin API of Ory Hydra. Nginx proxies requests only from the 172.28.0.0/24 subnet or by providing ?secret=CHANGE-ME-INSECURE-PASSWORD argument for additional security. The Admin API implements administrative endpoints to manage data or /health endpoints to check the availability of your instance. You should either not expose these endpoints or implement additional security checks for them.

You can find more information about endpoints in the Prepare for production guide.

Your full Nginx configuration should now look something like this:

upstream public_api {
server 127.0.0.1:4444;
server 127.0.0.1:4444;
}
upstream admin_api {
server 127.0.0.1:4445;
server 127.0.0.1:4445;
}
server {
listen 80;
server_name oauth2.example.com;
if ($host = oauth2.example.com) {
return 301 https://$host$request_uri;
}
}
server {
listen [::]:443 ssl ipv6only=on;
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/oauth2.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/oauth2.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location ~ ^/(.well-known|oauth2/auth|oauth2/token|oauth2/revoke|oauth2/fallbacks/consent|oauth2/fallbacks/error|userinfo)/? {
proxy_pass http://public_api;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;

}
location ~ ^/(clients|keys|health|metrics|version|oauth2/auth/requests|oauth2/introspect|oauth2/flush)/? {
set $allow 0;
if ($remote_addr ~* "172.28.0.*") {
set $allow 1;
}
if ($arg_secret = "CHANGE-ME-INSECURE-PASSWORD") {
set $allow 1;
}
if ($allow = 0) {
return 403;
}

rewrite /admin/(.*) /$1 break;

proxy_pass http://admin_api;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}

}
  1. Test the Nginx configuration:

    nginx -t

    If your configuration is correct, you should get the following outputs:

    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  2. Now reload the Nginx service:

    service nginx reload
  3. Test if Ory Hydra runs by doing a health check at https://oauth2.example.com/health?secret=CHANGE-ME-INSECURE-PASSWORD.

Next Steps