#3412·patroni

patroni --generate-config hba rules

Author: aleszelenyCreated Jul 16, 2025Updated Jul 16, 2025
Labelsbug

What happened?

While converting an existing PG instance to patroni, the generate config writes HBA rules under the top-level postgresql key. The patroni configuration manual page states that the local configuration file has precedence over configuration stored in DCS.

As a result, any subsequent calls of patronictl edit-config correctly modify hba entries in DCS, while the configuration file (used the default, otherwise patronni will not manage pg_hba configuration as per the manual) is kept untouched, because it matches the local configuration file.

Therefore, following the conversion manual instructions using generate-config ends up in a setup that does not allow editing pg_hba.conf file using patronictl/REST API.

How can we reproduce it (as minimally and precisely as possible)?

Install Patroni on a host where a PostgreSQL cluster is running and issue:

PATRONI_POSTGRESQL_BIN_DIR=/usr/pgsql-16/bin/
patroni --generate-config

Adjust the variable PATRONI_POSTGRESQL_BIN_DIR based on your installation.

What did you expect to happen?

For converting an existing PG isntance to Patroni, I'd expect pg_hba key to be placed under bootstrap.dcs.postgresql key. That way, it'll be imported to DCS, and subsequent HBA changes in DCS will be reflected on the patroni nodes.

This is based on the expectation that an existing PG instance is being converted to "patroni-managed" with the aim of managing the PostgreSQL instance by patroni (ctl / REST API), therefore, placing HBA configuration in the local yaml configuration file by the generate-config command persists the state, and the central management in DCS does not apply its changes (change requires editing local yaml and reloading patroni).

At the end of the existing instance conversion, I'd expect (hopefully correctly) that the instance will be fully managed by patronictl/RESTAPI.

Patroni/PostgreSQL/DCS version

  • Patroni version: 4.0.6
  • PostgreSQL version: 16.6
  • DCS (and its version): 3.6.1
  • OS: AlmaLinux 9

Patroni configuration file

yaml
# not yet exists while converting the existing instance

patronictl show-config

yaml
# not yet exists while converting the existing instance
# added --generate-config output instead
scope: test_cluster_01
name: pgsql-patroni-node-2

log:
  format: '%(asctime)s %(levelname)s: %(message)s'
  level: INFO
  max_queue_size: 1000
  traceback_level: ERROR
  type: plain

restapi:
  connect_address: 10.36.0.202:8008
  listen: 10.36.0.202:8008

# The bootstrap configuration. Works only when the cluster is not yet initialized.
# If the cluster is already initialized, all changes in the `bootstrap` section are ignored!
bootstrap:
  # This section will be written into <dcs>:/<namespace>/<scope>/config after initializing
  # new cluster and all other cluster members will use it as a `global configuration`.
  # WARNING! If you want to change any of the parameters that were set up
  # via `bootstrap.dcs` section, please use `patronictl edit-config`!
  dcs:
    loop_wait: 10
    retry_timeout: 10
    ttl: 30
    postgresql:
      parameters:
        DateStyle: ISO, MDY
        TimeZone: Europe/Prague
        archive_mode: 'on'
        archive_timeout: 15min
        autovacuum: 'on'
        autovacuum_analyze_scale_factor: '0.05'
        autovacuum_naptime: 10s
        autovacuum_vacuum_cost_limit: '5000'
        autovacuum_vacuum_scale_factor: '0.05'
        bgwriter_delay: 100ms
        bgwriter_lru_maxpages: '300'
        checkpoint_completion_target: '0.9'
        checkpoint_timeout: 30min
        client_connection_check_interval: 10s
        cron.database_name: bench
        default_statistics_target: '2500'
        default_text_search_config: pg_catalog.english
        dynamic_shared_memory_type: posix
        effective_cache_size: 4GB
        effective_io_concurrency: '100'
        full_page_writes: 'on'
        hot_standby: 'on'
        hot_standby_feedback: 'on'
        huge_pages: 'on'
        icu_validation_level: error
        idle_in_transaction_session_timeout: 5min
        jit: 'off'
        jit_provider: llvmjit
        lc_messages: en_US.UTF-8
        lc_monetary: en_US.UTF-8
        lc_numeric: en_US.UTF-8
        lc_time: en_US.UTF-8
        log_autovacuum_min_duration: '0'
        log_checkpoints: 'on'
        log_connections: 'on'
        log_destination: stderr, jsonlog
        log_directory: log
        log_disconnections: 'on'
        log_filename: postgresql.log
        log_line_prefix: '%m, [%p], [%P], [%c], [%l], [%s], [%b], [%x], %q[%d], [%u],
          [%r], [%a], [%Q], [%i], '
        log_lock_waits: 'on'
        log_min_duration_sample: 1ms
        log_min_duration_statement: 500ms
        log_min_error_statement: warning
        log_recovery_conflict_waits: 'on'
        log_rotation_age: '0'
        log_rotation_size: '0'
        log_statement: ddl
        log_statement_sample_rate: '0.001'
        log_temp_files: '0'
        log_timezone: Europe/Prague
        log_transaction_sample_rate: '0.001'
        log_truncate_on_rotation: 'off'
        logging_collector: 'on'
        maintenance_work_mem: 64MB
        max_connections: '500'
        max_locks_per_transaction: '64'
        max_logical_replication_workers: '12'
        max_prepared_transactions: '0'
        max_replication_slots: '25'
        max_stack_depth: 4MB
        max_wal_senders: '30'
        max_wal_size: 1GB
        max_worker_processes: '32'
        min_wal_size: 1GB
        old_snapshot_threshold: 8h
        password_encryption: md5
        pg_stat_kcache.track: all
        pg_stat_kcache.track_planning: 'on'
        pg_stat_statements.max: '10000'
        pg_stat_statements.track: all
        pg_wait_sampling.profile_pid: 'off'
        primary_conninfo: dbname=postgres user=app_patroni_replication host=10.36.0.201
          port=5432 sslmode=prefer application_name=pgsql-patroni-node-2 gssencmode=prefer
          channel_binding=prefer
        primary_slot_name: pgsql_patroni_node_2
        random_page_cost: '1'
        reserved_connections: '20'
        shared_buffers: 1GB
        shared_preload_libraries: pg_stat_statements, pg_stat_kcache, pg_qualstats, pg_cron,
          pg_partman_bgw, pg_wait_sampling, plpgsql_check
        tcp_keepalives_count: '0'
        tcp_keepalives_idle: '0'
        tcp_keepalives_interval: '0'
        temp_buffers: 16MB
        track_activities: 'on'
        track_activity_query_size: 4kB
        track_commit_timestamp: 'on'
        track_counts: 'on'
        track_functions: all
        track_io_timing: 'on'
        track_wal_io_timing: 'on'
        unix_socket_directories: /run/postgresql, /tmp
        vacuum_buffer_usage_limit: 512MB
        wal_decode_buffer_size: 256MB
        wal_keep_size: 2GB
        wal_level: logical
        wal_log_hints: 'on'
        wal_receiver_timeout: 15min
        wal_sender_timeout: 15min
        work_mem: 4MB
      use_slots: true

postgresql:
  authentication:
    replication:
      password: '#FIXME'
      username: '#FIXME'
    superuser:
      password: ''
      username: postgres
  bin_dir: /usr/pgsql-16/bin
  connect_address: 10.36.0.202:5432
  data_dir: /pgsql/cluster/16/data
  listen: '*:5432'
  parameters:
    archive_command: pgbackrest --stanza=uat_patroni_test archive-push "%p"
    config_file: /pgsql/cluster/16/data/postgresql.conf
    hba_file: /pgsql/cluster/16/data/pg_hba.conf
    ident_file: /pgsql/cluster/16/data/pg_ident.conf
    restore_command: pgbackrest --stanza=uat_patroni_test archive-get %f "%p"
  pg_hba:
  - local   all             all                                           peer
  - host    all             all                        127.0.0.1/32       md5
  - host    all             all                        ::1/128            md5
  - host    replication     app_patroni_replication    10.36.0.0/15       md5
  - 'local   replication     all                                           peer    #
    pg_basebackup stream mode using socket'
  - 'host    replication     all                        127.0.0.1/32       md5     #
    postgres user without password won''t be able to connect to localhost (even for
    basebackup)'
  - 'host    replication     all                        ::1/128            md5     #
    same as above'
  - 'host    all             +legacy_users              samenet            md5 # Allow
    connections from any address in any subnet that the server is directly connected
    to.'
  - host    all             all                        0.0.0.0/0          reject
  - host    all             all                        ::0/0              reject
  pg_ident:
  - testmap1 root postgres

tags:
  clonefrom: true
  failover_priority: 1
  noloadbalance: false
  nostream: false
  nosync: false

Patroni log files

bash
# not yet exists while converting the existing instance

PostgreSQL log files

bash
No errors

Have you tried to use GitHub issue search?

  • Yes

Anything else we need to know?

Removing the postgresql.pg_hba key from the local configuration file makes patronictl edit-config work as expected, even for HBA file entries.

Might it be that all the postgresql.parameters shall be under the bootstrap.dcs key, but I do not have enough knowledge/context to suggest such a change globally for the generate-config command.

Out of curiosity, is the fact that the generate-config command also creates postgresql.parameters.hba_file, but as it matches the default location in my test case, it does not prevent Patroni from properly managing HBA file entries.