mTLS via Yubikey + cloudflare's pingora


I wanted to share some details about how to create an instance of nextcloud with the following features / properties:

  • Cloudflare’s pingora as SSL termination reverse proxy, which runs in distroless docker image
  • Your own CA certificate securely generated and kept on Yubikey
    • along with server certificates
    • and client certificates (so that browser has to provide proof/certificate for server)

What is pingora and Why pingora?

It is safe (written in Rust) and resource-efficient server by Cloudflare:

Today we are excited to talk about Pingora, a new HTTP proxy we’ve built in-house using Rust that serves over 1 trillion requests a day, boosts our performance, and enables many new features for Cloudflare customers, all while requiring only a third of the CPU and memory resources of our previous proxy infrastructure.

Why mTLS and client certificates?

In short: scheme when CAs (authorities) issues certificates is broken.

It is broken in the sense that you have to trust CA. There’s no inherent technical mechanism within the X.509 standard that prevents a rogue CA from issuing a certificate for any domain.

And this happens:

security biz MCS Holdings has created unauthorized SSL certificates for some Google-owned websites. MCS has said in a statement "the reported issue is a human mistake that took place unintentionally through a single PC inside MCS Lab which had been dedicated for testing purposes

ACME, the automated issuance protocol used by Let’s Encrypt, suffers from a cryptographic flaw that would allow attackers to fraudulently obtain certificates for domains they don’t control. The flaw had gone undetected during a formal security audit. Fortunately, the flaw is discovered and fixed before Let’s Encrypt goes live

See more examples

After all, if it were NOT broken, Google would not be using Certificate pinning in its apps.

And while mTLS does not prevent your client from connecting to a MITM, MITM will never be able to connect to server (server will reject rogue MITM), so MITM will not get any data.

Yubikey as CA / PKI

In a nutshell, there are just 3.5 relevant commands that you need to use:

  • yu-pki-reset-piv: resets PIV application in Yubikey (just in case you need to erase PIV)

  • yu-pki-generate-keypair-and-cert-ca: generates private ECCP384 key and self-signed certificate directly on Yubikey (which means that private key never touches your disk which is important, because anything that have ever touched disk can be assumed that will live there forever)

  • yu-pki-generate-keypair-and-cert-server --serial 1111111 --host asdf.qwer --host zxcv.qwer --ip will generate private key and certificate for server.
    These should then be copied to a server and they are specified for proxy in the following way:

    - --priv-key=/keys/server-key.pem
    - --cert=/keys/server-cert.pem
    - --ca=/keys/CA.crt.pem
  • yu-pki-generate-keypair-and-cert-client will generate CA and private key for client. You will need to install them into browser / Android:

    • Install on Android: SettingsSecurityMore Security Settings
      Encryption and credentialsInstall a certificate:
      • CA Certificate: install CA pem
      • VPN and app user certificate: install the bundle, you will need to provide password
    • Install in Edge:
      • Settings → Privacy and security → Authorities
      • Import
      • Open CA.crt.pem


compose.yml file consists of 3 things:

  • pproxy: it is built directly on server, using distroless Rust image
  • mariadb official image
  • nextcloud

deploying / starting

Finally, these are the commands that will deploy nextcloud instance to a remote ssh host:

  • (only first time) Generate server key:
    yu-pki-generate-keypair-and-cert-server --serial 111 --host --ip --dump-private-key (see above)
  • (only first time) Copy the key to remote server: nc-bootstrap --dir /dir/with/keys
  • Copy dockerfiles & pproxy to remote ssh and build images there: nc-deploy
  • Start (bare) nextcloud: nc-start
  • (only once) Install apps & users: nc-init-from-afresh

There are also commands for backup & restore:


Finally, there are just 6 placeholders that you need to set:

$ rg -uuu TODO .

11:set NC_REMOTE_SSH TODO                  # Remote SSH server
14:set NC_BACKUP     /TODO/NextCloud       # Local directory with backup
116:      php occ user:add --password-from-env --no-interaction TODO;                         \



All the scripts are written in fish shell, you need to source them to make the functions available