Skip to content

Rate this page
Thanks for your feedback
Thank you! The feedback has been submitted.

Get free database assistance or contact our experts for personalized support.

Generate certificates manually

You can customize TLS for the Operator by providing your own TLS certificates. To do this, you must create two Kubernetes Secret objects before deploying your cluster:

  • One for external communication, later referenced by the spec.secrets.customTLSSecret field in the deploy/cr.yaml
  • One for internal communication (used for replication authentication), referenced by the spec.secrets.customReplicationTLSSecret field in the deploy/cr.yaml.

Each Secret must contain the following fields:

  • tls.crt (the TLS certificate)
  • tls.key (the TLS private key)
  • ca.crt (the Certificate Authority certificate)

Note that you cannot use only one custom set of certificates. If you provide a custom TLS Secret, you must also provide a custom replication TLS Secret, and both must contain the same ca.crt.

Provide existing custom certificates

For example, you have files named ca.crt, my_tls.key, and my_tls.crt. Run the following command to create a custom TLS Secret named cluster1-tls:

kubectl create secret generic -n postgres-operator cluster1-tls \
  --from-file=ca.crt=ca.crt \
  --from-file=tls.key=my_tls.key \
  --from-file=tls.crt=my_tls.crt

In the same way, create the custom TLS replication Secret, for example replication1-tls.

Next, reference your Secrets in the deploy/cr.yaml Custom Resource manifest as follows:

  • add a Secret created for the external use to the secrets.customTLSSecret.name field
  • add a Secret created for internal communications to the secrets.customReplicationTLSSecret.name field

Here’s the sample configuration:

spec:
  ...
  secrets:
    customTLSSecret:
      name: cluster1-tls
    customReplicationTLSSecret:
      name: replication1-tls
  ...

Now you can create a cluster with your custom certificates:

kubectl apply -f deploy/cr.yaml

Provide a pre-existing custom root CA certificate to the Operator

You can also provide a custom root CA certificate to the Operator. In this case the Operator will not generate one itself, but will use the user-provided CA certificate. This can be useful if you would like to have several database clusters with certificates generated by the Operator based on the same root CA.

To make the Operator use a custom root certificate, create a separate secret with this certificate and specify this secret in the Custom Resource options before you deploy a cluster.

For example, if you have files named my_tls.key and my_tls.crt stored on your local machine, you could run the following command to create a Secret named cluster1-ca-cert in the postgres-operator namespace:

kubectl create secret generic -n postgres-operator cluster1-ca-cert \
  --from-file=tls.crt=my_tls.crt \
  --from-file=tls.key=my_tls.key

You also need to specify details about this secret in your deploy/cr.yaml manifest:

...
secrets:
  customRootCATLSSecret:
    name: cluster1-ca-cert
    items:
      - key: "tls.crt"
        path: "root.crt"
      - key: "tls.key"
        path: "root.key"

Now, you can create the cluster with the kubectl apply -f deploy/cr.yaml command. The Operator should use the root CA certificate you had provided.

Warning

This approach allows using root CA certificate auto-generated by the Operator for some other clusters, but it needs caution. If the cluster with auto-generated certificate has delete-ssl finalizer enabled, the certificate will be deleted at the cluster deletion event even if it was manually provided to some other cluster.

Generate custom certificates for the Operator yourself

Understand certificate requirements

To find out the certificates specifics needed for the Operator, view the certificates generated by the Operator automatically. For example, if you have a cluster deployed in some staging environment.

Here’s how to do it:

  1. Check the secrets created by the Operator:

    kubectl get secrets
    
    Expected output
    cluster1-cluster-ca-cert        Opaque   2      143m
    cluster1-cluster-cert           Opaque   3      143m
    cluster1-instance1-frdm-certs   Opaque   6      143m
    cluster1-instance1-qcqk-certs   Opaque   6      143m
    cluster1-instance1-wq55-certs   Opaque   6      143m
    cluster1-pgbackrest             Opaque   5      143m
    cluster1-pgbouncer              Opaque   6      143m
    cluster1-pguser-cluster1        Opaque   12     143m
    cluster1-replication-cert       Opaque   3      143m
    

    The Secrets of interest are cluster1-cluster-cert for external communication and cluster1-replication-cert for internal communication.

  2. You can examine the auto-generated CA certificate (ca.crt) as follows:

    kubectl get secret/cluster1-cluster-cert -o jsonpath='{.data.ca\.crt}' | base64 --decode | openssl x509 -text -noout
    
    Expected output
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number:
                ec:f3:d6:f5:35:5c:97:0c:66:cc:90:ed:e6:4b:0a:07
            Signature Algorithm: ecdsa-with-SHA384
            Issuer: CN = postgres-operator-ca
            Validity
                Not Before: Dec 24 13:58:21 2023 GMT
                Not After : Dec 21 14:58:21 2033 GMT
            Subject: CN = postgres-operator-ca
            Subject Public Key Info:
            ...
        ...
    
  3. You can check the auto-generated TLS certificate (tls.crt) in a similar way:

    kubectl get secret/cluster1-cluster-cert -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout
    
    Expected output
    Certificate:
        Data:
           Version: 3 (0x2)
           Serial Number:
               43:ac:81:65:4e:c6:1b:15:db:ca:36:c4:16:96:79:1b
           Signature Algorithm: ecdsa-with-SHA384
           Issuer: CN=postgres-operator-ca
           Validity
               Not Before: Jul 22 08:15:42 2025 GMT
               Not After : Jul 22 09:15:42 2026 GMT
           Subject: CN=cluster1-primary.default.svc.cluster.local.
           Subject Public Key Info:
               Public Key Algorithm: id-ecPublicKey
                   Public-Key: (256 bit)
                   pub:
                       04:cd:06:b5:27:67:64:2b:a3:9e:84:e6:31:81:7f:
                       3f:a9:ae:c9:da:bd:b8:76:3e:f0:09:bd:b8:eb:03:
                       88:c2:d3:4b:2a:1f:e9:5b:97:cf:4e:7b:b3:12:2b:
                       47:ee:a6:24:fb:29:ae:01:74:e2:4c:5c:3e:f9:8d:
                       cb:ff:0a:62:8d
                   ASN1 OID: prime256v1
                   NIST CURVE: P-256
           X509v3 extensions:
               X509v3 Key Usage: critical
                   Digital Signature, Key Encipherment
               X509v3 Basic Constraints: critical
                   CA:FALSE
               X509v3 Authority Key Identifier:
                   59:98:FE:88:1B:54:A0:7D:DD:20:A0:F6:29:08:05:C7:18:38:7C:92
               X509v3 Subject Alternative Name:
                   DNS:cluster1-primary.default.svc.cluster.local., DNS:cluster1-primary.default.svc, DNS:cluster1-primary.default, DNS:cluster1-primary, DNS:cluster1-replicas.default.svc.cluster.local., DNS:cluster1-replicas.default.svc, DNS:cluster1-replicas.default, DNS:cluster1-replicas
        Signature Algorithm: ecdsa-with-SHA384
        ...
    
    kubectl get secret/cluster1-replication-cert -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout
    
    Expected output
    Certificate:
         Data:
              Version: 3 (0x2)
              Serial Number:
                  31:1b:1e:ca:06:e6:98:4d:7e:de:6d:1b:68:d8:53:0e
              Signature Algorithm: ecdsa-with-SHA384
              Issuer: CN=postgres-operator-ca
              Validity
                  Not Before: Jul 22 08:15:42 2025 GMT
                  Not After : Jul 22 09:15:42 2026 GMT
              Subject: CN=_crunchyrepl
              Subject Public Key Info:
                  Public Key Algorithm: id-ecPublicKey
                      Public-Key: (256 bit)
                      pub:
                          04:b1:f7:9d:cd:33:0d:a5:19:a3:f2:fd:f6:b3:cd:
                          e1:a5:e4:19:11:ec:18:db:fe:9c:a8:7e:eb:d2:27:
                          59:d1:ef:3b:09:24:58:21:6a:54:60:30:1c:be:b0:
                          7a:39:c5:91:6f:01:ee:d1:0b:23:86:0c:16:cf:fc:
                          7d:7e:39:cb:0e
                      ASN1 OID: prime256v1
                      NIST CURVE: P-256
              X509v3 extensions:
                  X509v3 Key Usage: critical
                      Digital Signature, Key Encipherment
                  X509v3 Basic Constraints: critical
                      CA:FALSE
                  X509v3 Authority Key Identifier:
                      59:98:FE:88:1B:54:A0:7D:DD:20:A0:F6:29:08:05:C7:18:38:7C:92
                  X509v3 Subject Alternative Name:
                      DNS:_crunchyrepl
        Signature Algorithm: ecdsa-with-SHA384
        ...
    

Both secrets share the same ca.crt certificate but have different tls.crt certificates. The tls.crt in the Secret for external communications should have a Common Name (CN) setting that matches the primary Service name (CN = cluster1-primary.default.svc.cluster.local. in the above example). Similarly, the tls.crt in the Secret for internal communications should have a Common Name (CN) setting that matches the preset replication user: CN=_crunchyrepl.

Generate certificates

One of the options to create certificates yourself is to use CloudFlare PKI and TLS toolkit .

You must generate certificates twice: one set is for external communications, and another set is for internal ones!

Let’s say that your cluster name is cluster1 and the desired namespace is postgres-operator. The commands to generate certificates may look as follows:

  1. Set cluster context

    export CLUSTER_NAME=cluster1
    export NAMESPACE=postgres-operator
    
  2. Generate the root CA certificate:

    cat <<EOF | cfssl gencert -initca - | cfssljson -bare ca
      {
        "CN": "*",
        "key": {
          "algo": "ecdsa",
          "size": 384
        }
      }
      EOF
    
    Expected output
    2025/07/22 18:44:00 [INFO] generating a new CA key and certificate from CSR
    2025/07/22 18:44:00 [INFO] generate received request
    2025/07/22 18:44:00 [INFO] received CSR
    2025/07/22 18:44:00 [INFO] generating key: ecdsa-384
    2025/07/22 18:44:00 [INFO] encoded CSR
    2025/07/22 18:44:00 [INFO] signed certificate with serial number         558041563526770695468617559855840603242491856749
    

    You should have the following files:

    • ca-key.pem – CA private key
    • ca.pem – CA certificate
  3. Define the CA signing policy for certificates signed by the CA.

    cat <<EOF > ca-config.json
      {
         "signing": {
           "default": {
              "expiry": "87600h",
              "usages": ["digital signature", "key encipherment", "content commitment"]
            }
         }
      }
      EOF
    

Explanation of the values:

  • expiry - sets the lifetime for the certificates
  • usages specifies what the certificate is valid for:

    • digital signature: for signing data
    • key encipherment: for secure key exchange
    • content commitment: ensures data integrity
  • Generate the custom TLS certificates for external communication and sign them using the previously created CA certificate. These certificates have the Common Name (CN) cluster1-primary.postgres-operator.svc.cluster.local

    cat <<EOF | cfssl gencert -ca=ca.pem  -ca-key=ca-key.pem -config=./ca-config.json - | cfssljson -bare server
      {
         "hosts": [
           "localhost",
           "${CLUSTER_NAME}-primary",
           "${CLUSTER_NAME}-primary.${NAMESPACE}",
           "${CLUSTER_NAME}-primary.${NAMESPACE}.svc.cluster.local",
           "${CLUSTER_NAME}-primary.${NAMESPACE}.svc",
           "${CLUSTER_NAME}-replicas.${NAMESPACE}.svc.cluster.local",
           "${CLUSTER_NAME}-replicas.${NAMESPACE}.svc",
           "${CLUSTER_NAME}-replicas.${NAMESPACE}",
           "${CLUSTER_NAME}-tls-replicas"
         ],
         "CN": "${CLUSTER_NAME}-primary.${NAMESPACE}.svc.cluster.local", 
         "key": {
           "algo": "ecdsa",
           "size": 384
         }
      }
    EOF
    

    You should have the following files as defined by the -bare server part of the command:

    • server.pem - the signed certificate
    • server-key.pem - the private key
  • Generate the custom TLS certificates for internal communication and sign them using the previously created CA certificate. These certificates have the Common Name (CN) _crunchyrepl.

    cat <<EOF | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=./ca-config.json - | cfssljson -bare replication
      {
        "CN": "_crunchyrepl",
        "key": {
          "algo": "ecdsa",
          "size": 384
        }
      }
      EOF
    

    You should have the following files as defined by the -bare part of the command:

    • replication.pem - the signed certificate
    • replication-key.pem - the private key

You can find more on generating certificates this way in official Kubernetes documentation .

Refer to the Provide pre-existing custom certificates section for the steps to create Secrets and configure the Operator. Replace the values with your files.


Last update: April 1, 2026
Created: July 12, 2022