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
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
Install PostgreSQL by running the following command:
sudo apt install postgresql postgresql-contrib
sudo -i -u postgresCreate the database:
createdb hydra
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 configurationCreate a Postgres user for Hydra:
CREATE USER hydra PASSWORD '<YOUR_PASSWORD_HERE>';
Edit the
/etc/postgresql/12/main/pg_hba.conf
file and add the following to enablescram-sha-256
encryption:host all all 127.0.0.1/32 scram-sha-256
Check your credentials against Postgres:
psql -U hydra -W -h 127.0.0.1
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
Create a new user with prohibited login:
useradd -s /bin/false -m -d /opt/hydra hydra
Create folders to hold the installation data and configuration files:
mkdir /opt/hydra/{bin,config}
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 LICENSEDownload the Quickstart configuration files
cd ../config
wget https://raw.githubusercontent.com/ory/hydra/<version-you-want>/contrib/quickstart/5-min/hydra.ymlAdd configuration for Ory Hydra in
hydra.yml
to use the Postgres database. Openhydra.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 theissuer
URL. This URL will be used asissuer
in access and ID tokens.dsn: postgres://hydra:b0qw68gr3Q@127.0.0.1:5432/hydra?sslmode=disable&max_conns=20&max_idle_conns=4
urls:
self:
issuer: https://oauth2.example.comApply migrations:
/opt/hydra/bin/hydra -c /opt/hydra/config/hydra.yml migrate sql -y postgres://hydra:b0qw68gr3Q@127.0.0.1:5432/hydra?sslmode=disable
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
Change the owner of
/opt/hydra
directory to thehydra
user:chown -R hydra /opt/hydra/
Create a
/etc/systemd/system/kratos.service
filecd /etc/systemd/system
nano hydra.serviceAdd 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.
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.Start hydra.service using systemd:
systemctl start hydra.service
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.
Install Nginx (and certbot) by running:
apt install nginx certbot python3-certbot-nginx
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 contentcd /etc/nginx/sites-available/
nano oauth2.example.comserver {
listen 80;
server_name oauth2.example.com;
}Create a symlink to
sites-enabled
directory:ln -s /etc/nginx/sites-available/oauth2.example.com /etc/nginx/sites-enabled/oauth2.example.com
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.
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 {
...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 the172.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;
}
}
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 successfulNow reload the Nginx service:
service nginx reload
Test if Ory Hydra runs by doing a health check at
https://oauth2.example.com/health?secret=CHANGE-ME-INSECURE-PASSWORD
.