diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 2981a0851..cbd14f7c6 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -1,77 +1,77 @@
 {
     "system_conntrack": {
         "conntrack_sync": ["service_conntrack-sync"],
         "vrf": ["vrf"]
     },
     "firewall": {
         "conntrack": ["system_conntrack"],
         "group_resync": ["system_conntrack", "nat", "policy_route"]
     },
     "interfaces_bonding": {
         "ethernet": ["interfaces_ethernet"]
     },
     "interfaces_bridge": {
         "vxlan": ["interfaces_vxlan"],
         "wlan": ["interfaces_wireless"]
     },
     "load_balancing_wan": {
         "conntrack": ["system_conntrack"]
     },
     "nat": {
         "conntrack": ["system_conntrack"]
     },
     "nat66": {
         "conntrack": ["system_conntrack"]
     },
     "pki": {
         "ethernet": ["interfaces_ethernet"],
         "openvpn": ["interfaces_openvpn"],
+        "haproxy": ["load-balancing_haproxy"],
         "https": ["service_https"],
         "ipsec": ["vpn_ipsec"],
         "openconnect": ["vpn_openconnect"],
-        "reverse_proxy": ["load-balancing_reverse-proxy"],
         "rpki": ["protocols_rpki"],
         "sstp": ["vpn_sstp"],
         "sstpc": ["interfaces_sstpc"],
         "stunnel": ["service_stunnel"]
     },
     "vpn_ipsec": {
         "nhrp": ["protocols_nhrp"]
     },
     "vpn_l2tp": {
         "ipsec": ["vpn_ipsec"]
     },
     "qos": {
         "bonding": ["interfaces_bonding"],
         "bridge": ["interfaces_bridge"],
         "dummy": ["interfaces_dummy"],
         "ethernet": ["interfaces_ethernet"],
         "geneve": ["interfaces_geneve"],
         "input": ["interfaces_input"],
         "l2tpv3": ["interfaces_l2tpv3"],
         "loopback": ["interfaces_loopback"],
         "macsec": ["interfaces_macsec"],
         "openvpn": ["interfaces_openvpn"],
         "pppoe": ["interfaces_pppoe"],
         "pseudo-ethernet": ["interfaces_pseudo-ethernet"],
         "tunnel": ["interfaces_tunnel"],
         "vti": ["interfaces_vti"],
         "vxlan": ["interfaces_vxlan"],
         "wireguard": ["interfaces_wireguard"],
         "wireless": ["interfaces_wireless"],
         "wwan": ["interfaces_wwan"]
     },
     "system_wireless": {
         "wireless": ["interfaces_wireless"]
     },
     "system_ip": {
         "sysctl": ["system_sysctl"]
     },
     "system_ipv6": {
         "sysctl": ["system_sysctl"]
     },
     "system_option": {
         "ip_ipv6": ["system_ip", "system_ipv6"],
         "sysctl": ["system_sysctl"]
     }
 }
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index baa1e9110..35587b63c 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -1,35 +1,35 @@
 [
 "accelppp.py",
 "bgp.py",
 "bonding.py",
 "bridge.py",
 "cgnat.py",
 "config_mgmt.py",
 "conntrack.py",
 "container.py",
 "cpu.py",
 "dhcp.py",
 "dns.py",
 "evpn.py",
 "interfaces.py",
 "ipsec.py",
 "lldp.py",
 "log.py",
 "memory.py",
 "multicast.py",
 "nat.py",
 "neighbor.py",
 "nhrp.py",
 "openconnect.py",
 "openvpn.py",
 "otp.py",
 "qos.py",
 "reset_vpn.py",
-"reverseproxy.py",
+"load-balancing_haproxy.py",
 "route.py",
 "storage.py",
 "system.py",
 "uptime.py",
 "version.py",
 "vrf.py"
 ]
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index 5137966c1..786ebfb21 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -1,231 +1,231 @@
-### Autogenerated by load-balancing_reverse-proxy.py ###
+### Autogenerated by load-balancing_haproxy.py ###
 
 global
     chroot /var/lib/haproxy
     stats socket /run/haproxy/admin.sock mode 660 level admin
     stats timeout 30s
     user haproxy
     group haproxy
     daemon
 
 {% if global_parameters is vyos_defined %}
 {%     if global_parameters.logging is vyos_defined %}
 {%         for facility, facility_config in global_parameters.logging.facility.items() %}
     log /dev/log {{ facility }} {{ facility_config.level }}
 {%         endfor %}
 {%     endif %}
 {%     if global_parameters.max_connections is vyos_defined %}
     maxconn {{ global_parameters.max_connections }}
 {%     endif %}
 
     # Default SSL material locations
     ca-base /etc/ssl/certs
     crt-base /etc/ssl/private
 
 {%     if global_parameters.ssl_bind_ciphers is vyos_defined %}
     # https://ssl-config.mozilla.org/#server=haproxy&version=2.6.12-1&config=intermediate&openssl=3.0.8-1&guideline=5.6
     ssl-default-bind-ciphers {{ global_parameters.ssl_bind_ciphers | join(':') | upper }}
 {%     endif %}
     ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
 {%     if global_parameters.tls_version_min is vyos_defined('1.3') %}
     ssl-default-bind-options force-tlsv13
 {%     else %}
     ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
 {%     endif %}
 {% endif %}
 
 defaults
     log     global
     mode    http
     option  dontlognull
     timeout connect 10s
     timeout client  50s
     timeout server  50s
     errorfile 400 /etc/haproxy/errors/400.http
     errorfile 403 /etc/haproxy/errors/403.http
     errorfile 408 /etc/haproxy/errors/408.http
     errorfile 500 /etc/haproxy/errors/500.http
     errorfile 502 /etc/haproxy/errors/502.http
     errorfile 503 /etc/haproxy/errors/503.http
     errorfile 504 /etc/haproxy/errors/504.http
 
 # Frontend
 {% if service is vyos_defined %}
 {%     for front, front_config in service.items() %}
 frontend {{ front }}
 {%         set ssl_front = [] %}
 {%         if front_config.ssl.certificate is vyos_defined and front_config.ssl.certificate is iterable %}
 {%             for cert in front_config.ssl.certificate %}
 {%                 set _ = ssl_front.append('crt /run/haproxy/' ~ cert ~ '.pem') %}
 {%             endfor %}
 {%         endif %}
 {%         set ssl_directive = 'ssl' if ssl_front else '' %}
 {%         if front_config.listen_address is vyos_defined %}
 {%             for address in front_config.listen_address %}
     bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_directive }} {{ ssl_front | join(' ') }}
 {%             endfor %}
 {%         else %}
     bind [::]:{{ front_config.port }} v4v6 {{ ssl_directive }} {{ ssl_front | join(' ') }}
 {%         endif %}
 {%         if front_config.redirect_http_to_https is vyos_defined %}
     http-request redirect scheme https unless { ssl_fc }
 {%         endif %}
 {%         if front_config.logging is vyos_defined %}
 {%             for facility, facility_config in front_config.logging.facility.items() %}
     log /dev/log {{ facility }} {{ facility_config.level }}
 {%             endfor %}
 {%         endif %}
     mode {{ front_config.mode }}
 {%         if front_config.tcp_request.inspect_delay is vyos_defined %}
     tcp-request inspect-delay {{ front_config.tcp_request.inspect_delay }}
 {%         endif %}
 {# add tcp-request related directive if ssl is configured #}
 {%         if front_config.mode == 'tcp' and front_config.rule is vyos_defined %}
 {%             for rule, rule_config in front_config.rule.items() %}
 {%                 if rule_config.ssl is vyos_defined %}
     tcp-request content accept if { req_ssl_hello_type 1 }
 {%                     break %}
 {%                 endif %}
 {%             endfor %}
 {%         endif %}
 {%         if front_config.http_response_headers is vyos_defined %}
 {%             for header, header_config in front_config.http_response_headers.items() %}
     http-response set-header {{ header }} '{{ header_config['value'] }}'
 {%             endfor %}
 {%         endif %}
 {%         if front_config.rule is vyos_defined %}
 {%             for rule, rule_config in front_config.rule.items() %}
     # rule {{ rule }}
 {%                 if rule_config.domain_name is vyos_defined %}
 {%                     set rule_options = 'hdr(host)' %}
 {%                     if rule_config.ssl is vyos_defined %}
 {%                         set ssl_rule_translate = {'req-ssl-sni': 'req_ssl_sni', 'ssl-fc-sni': 'ssl_fc_sni', 'ssl-fc-sni-end': 'ssl_fc_sni_end'} %}
 {%                         set rule_options = ssl_rule_translate[rule_config.ssl] %}
 {%                     endif %}
 {%                     for domain in rule_config.domain_name %}
     acl {{ rule }} {{ rule_options }} -i {{ domain }}
 {%                     endfor %}
 {%                 endif %}
 {# path url #}
 {%                 if rule_config.url_path is vyos_defined %}
 {%                     set path_mod_translate = {'begin': '-i -m beg', 'end': '-i -m end', 'exact': ''} %}
 {%                     for path, path_config in rule_config.url_path.items() %}
 {%                         for url in path_config %}
     acl {{ rule }} path {{ path_mod_translate[path] }} {{ url }}
 {%                         endfor %}
 {%                     endfor %}
 {%                 endif %}
 {%                 if rule_config.set.backend is vyos_defined %}
     use_backend {{ rule_config.set.backend }} if {{ rule }}
 {%                 endif %}
 {%                 if rule_config.set.redirect_location is vyos_defined %}
     http-request redirect location {{ rule_config.set.redirect_location }} code 301 if {{ rule }}
 {%                 endif %}
 {# endpath #}
 {%             endfor %}
 {%         endif %}
 {%         if front_config.backend is vyos_defined %}
 {%             for backend in front_config.backend %}
     default_backend {{ backend }}
 {%             endfor %}
 {%         endif %}
 
 {%     endfor %}
 {% endif %}
 
 # Backend
 {% if backend is vyos_defined %}
 {%     for back, back_config in backend.items() %}
 backend {{ back }}
 {%         if back_config.health_check is vyos_defined %}
 {%             if back_config.health_check == 'smtp' %}
     option smtpchk
 {%             else  %}
     option {{ back_config.health_check }}-check
 {%             endif %}
 {%         endif %}
 {%         if back_config.http_check is vyos_defined %}
     option httpchk
 {%         endif %}
 {%         set send = '' %}
 {%         if back_config.http_check.method is vyos_defined %}
 {%             set send = send + ' meth ' + back_config.http_check.method | upper %}
 {%         endif %}
 {%         if back_config.http_check.uri is vyos_defined %}
 {%             set send = send + ' uri ' + back_config.http_check.uri %}
 {%         endif %}
 {%         if send != '' %}
     http-check send{{ send }}
 {%         endif %}
 {%         if back_config.http_check.expect is vyos_defined %}
 {%             if back_config.http_check.expect.status is vyos_defined %}
     http-check expect status {{ back_config.http_check.expect.status }}
 {%             elif back_config.http_check.expect.string is vyos_defined %}
     http-check expect string {{ back_config.http_check.expect.string }}
 {%             endif %}
 {%         endif %}
 {%         if back_config.balance is vyos_defined %}
 {%             set balance_translate = {'least-connection': 'leastconn', 'round-robin': 'roundrobin', 'source-address': 'source'} %}
     balance {{ balance_translate[back_config.balance] }}
 {%         endif %}
 {# If mode is HTTP add X-Forwarded headers #}
 {%         if back_config.mode == 'http' %}
     option forwardfor
     http-request set-header X-Forwarded-Port %[dst_port]
     http-request add-header X-Forwarded-Proto https if { ssl_fc }
 {%         endif %}
 {%         if back_config.logging is vyos_defined %}
 {%             for facility, facility_config in back_config.logging.facility.items() %}
     log /dev/log {{ facility }} {{ facility_config.level }}
 {%             endfor %}
 {%         endif %}
     mode {{ back_config.mode }}
 {%         if back_config.http_response_headers is vyos_defined %}
 {%             for header, header_config in back_config.http_response_headers.items() %}
     http-response set-header {{ header }} '{{ header_config['value'] }}'
 {%             endfor %}
 {%         endif %}
 {%         if back_config.rule is vyos_defined %}
 {%             for rule, rule_config in back_config.rule.items() %}
 {%                 if rule_config.domain_name is vyos_defined and rule_config.set.server is vyos_defined %}
 {%                     set rule_options = 'hdr(host)' %}
 {%                     if rule_config.ssl is vyos_defined %}
 {%                         set ssl_rule_translate = {'req-ssl-sni': 'req_ssl_sni', 'ssl-fc-sni': 'ssl_fc_sni', 'ssl-fc-sni-end': 'ssl_fc_sni_end'} %}
 {%                         set rule_options = ssl_rule_translate[rule_config.ssl] %}
 {%                     endif %}
 {%                     for domain in rule_config.domain_name %}
     acl {{ rule }} {{ rule_options }} -i {{ domain }}
 {%                     endfor %}
     use-server {{ rule_config.set.server }} if {{ rule }}
 {%                 endif %}
 {# path url #}
 {%                 if rule_config.url_path is vyos_defined and rule_config.set.redirect_location is vyos_defined %}
 {%                     set path_mod_translate = {'begin': '-i -m beg', 'end': '-i -m end', 'exact': ''} %}
 {%                     for path, path_config in rule_config.url_path.items() %}
 {%                         for url in path_config %}
     acl {{ rule }} path {{ path_mod_translate[path] }} {{ url }}
 {%                         endfor %}
 {%                     endfor %}
     http-request redirect location {{ rule_config.set.redirect_location }} code 301 if {{ rule }}
 {%                 endif %}
 {# endpath #}
 {%             endfor %}
 {%         endif %}
 {%         if back_config.server is vyos_defined %}
 {%             set ssl_back =  'ssl ca-file /run/haproxy/' ~ back_config.ssl.ca_certificate ~ '.pem' if back_config.ssl.ca_certificate is vyos_defined else ('ssl verify none' if back_config.ssl.no_verify is vyos_defined else '') %}
 {%             for server, server_config in back_config.server.items() %}
     server {{ server }} {{ server_config.address }}:{{ server_config.port }}{{ ' check' if server_config.check is vyos_defined }}{{ ' backup' if server_config.backup is vyos_defined }}{{ ' send-proxy' if server_config.send_proxy is vyos_defined }}{{ ' send-proxy-v2' if server_config.send_proxy_v2 is vyos_defined }} {{ ssl_back }}
 {%             endfor %}
 {%         endif %}
 {%         if back_config.timeout.check is vyos_defined %}
     timeout check {{ back_config.timeout.check }}s
 {%         endif %}
 {%         if back_config.timeout.connect is vyos_defined %}
     timeout connect {{ back_config.timeout.connect }}s
 {%         endif %}
 {%         if back_config.timeout.server is vyos_defined %}
     timeout server {{ back_config.timeout.server }}s
 {%         endif %}
 
 {%     endfor %}
 {% endif %}
diff --git a/debian/control b/debian/control
index 15fb5d72e..20cfcdc43 100644
--- a/debian/control
+++ b/debian/control
@@ -1,387 +1,387 @@
 Source: vyos-1x
 Section: contrib/net
 Priority: extra
 Maintainer: VyOS Package Maintainers <maintainers@vyos.net>
 Build-Depends:
   debhelper (>= 9),
   dh-python,
   fakeroot,
   gcc,
   iproute2,
   libvyosconfig0 (>= 0.0.7),
   libzmq3-dev,
   python3 (>= 3.10),
 # For QA
   pylint,
 # For generating command definitions
   python3-lxml,
   python3-xmltodict,
 # For running tests
   python3-coverage,
   python3-hurry.filesize,
   python3-netaddr,
   python3-netifaces,
   python3-nose,
   python3-jinja2,
   python3-paramiko,
   python3-passlib,
   python3-psutil,
   python3-requests,
   python3-setuptools,
   python3-tabulate,
   python3-zmq,
   quilt,
   whois
 Standards-Version: 3.9.6
 
 Package: vyos-1x
 Architecture: amd64 arm64
 Pre-Depends:
   libpam-runtime [amd64],
   libnss-tacplus [amd64],
   libpam-tacplus [amd64],
   libpam-radius-auth [amd64]
 Depends:
 ## Fundamentals
   ${python3:Depends} (>= 3.10),
   dialog,
   libvyosconfig0,
   libpam-cap,
   bash-completion,
   ipvsadm,
   udev,
   less,
   at,
   rsync,
   vyatta-bash,
   vyatta-biosdevname,
   vyatta-cfg,
   vyos-http-api-tools,
   vyos-utils,
 ## End of Fundamentals
 ## Python libraries used in multiple modules and scripts
   python3,
   python3-cryptography,
   python3-hurry.filesize,
   python3-inotify,
   python3-jinja2,
   python3-jmespath,
   python3-netaddr,
   python3-netifaces,
   python3-paramiko,
   python3-passlib,
   python3-pyroute2,
   python3-psutil,
   python3-pyhumps,
   python3-pystache,
   python3-pyudev,
   python3-six,
   python3-tabulate,
   python3-voluptuous,
   python3-xmltodict,
   python3-zmq,
 ## End of Python libraries
 ## Basic System services and utilities
   coreutils,
   sudo,
   systemd,
   bsdmainutils,
   openssl,
   curl,
   dbus,
   file,
   iproute2 (>= 6.0.0),
   linux-cpupower,
 # ipaddrcheck is widely used in IP value validators
   ipaddrcheck,
   ethtool (>= 6.10),
   lm-sensors,
   procps,
   netplug,
   sed,
   ssl-cert,
   tuned,
   beep,
   wide-dhcpv6-client,
 # Generic colorizer
   grc,
 ## End of System services and utilities
 ## For the installer
   fdisk,
   gdisk,
   mdadm,
   efibootmgr,
   libefivar1,
   dosfstools,
   grub-efi-amd64-signed [amd64],
   grub-efi-arm64-bin [arm64],
   mokutil [amd64],
   shim-signed [amd64],
   sbsigntool [amd64],
 # Image signature verification tool
   minisign,
 # Live filesystem tools
   squashfs-tools,
   fuse-overlayfs,
 ## End installer
   auditd,
   iputils-arping,
   iputils-ping,
   isc-dhcp-client,
 # For "vpn pptp", "vpn l2tp", "vpn sstp", "service ipoe-server"
   accel-ppp,
 # End "vpn pptp", "vpn l2tp", "vpn sstp", "service ipoe-server"
   avahi-daemon,
   conntrack,
   conntrackd,
 ## Conf mode features
 # For "interfaces wireless"
   hostapd,
   hsflowd,
   iw,
   wireless-regdb,
   wpasupplicant (>= 0.6.7),
 # End "interfaces wireless"
 # For "interfaces wwan"
   modemmanager,
   usb-modeswitch,
   libqmi-utils,
 # End "interfaces wwan"
 # For "interfaces openvpn"
   openvpn,
   openvpn-auth-ldap,
   openvpn-auth-radius,
   openvpn-otp,
   openvpn-dco,
   libpam-google-authenticator,
 # End "interfaces openvpn"
 # For "interfaces wireguard"
   wireguard-tools,
   qrencode,
 # End "interfaces wireguard"
 # For "interfaces pppoe"
   pppoe,
 # End "interfaces pppoe"
 # For "interfaces sstpc"
   sstp-client,
 # End "interfaces sstpc"
 # For "protocols *"
   frr (>= 9.1),
   frr-pythontools,
   frr-rpki-rtrlib,
   frr-snmp,
 # End "protocols *"
 # For "protocols nhrp" (part of DMVPN)
   opennhrp,
 # End "protocols nhrp"
 # For "protocols igmp-proxy"
   igmpproxy,
 # End "protocols igmp-proxy"
 # For "pki"
   certbot,
 # End "pki"
 # For "service console-server"
   conserver-client,
   conserver-server,
   console-data,
   dropbear,
 # End "service console-server"
 # For "service aws glb"
   aws-gwlbtun,
 # For "service dns dynamic"
   ddclient (>= 3.11.1),
 # End "service dns dynamic"
 # # For "service ids"
   fastnetmon [amd64],
   suricata,
   suricata-update,
 # End "service ids"
 # # For "service ndp-proxy"
   ndppd,
 # End "service ndp-proxy"
 # For "service router-advert"
   radvd,
 # End "service route-advert"
-# For "load-balancing reverse-proxy"
+# For "load-balancing haproxy"
   haproxy,
-# End "load-balancing reverse-proxy"
+# End "load-balancing haproxy"
 # For "load-balancing wan"
   vyatta-wanloadbalance,
 # End "load-balancing wan"
 # For "service dhcp-relay"
   isc-dhcp-relay,
 # For "service dhcp-server"
   kea,
 # End "service dhcp-server"
 # For "service lldp"
   lldpd,
 # End "service lldp"
 # For "service https"
   nginx-light,
 # End "service https"
 # For "service ssh"
   openssh-server,
   sshguard,
 # End "service ssh"
 # For "service salt-minion"
   salt-minion,
 # End "service salt-minion"
 # For "service snmp"
   snmp,
   snmpd,
 # End "service snmp"
 # For "service webproxy"
   squid,
   squidclient,
   squidguard,
 # End "service webproxy"
 # For "service monitoring node-exporter"
   node-exporter,
 # End "service monitoring node-exporter"
 # For "service monitoring telegraf"
   telegraf (>= 1.20),
 # End "service monitoring telegraf"
 # For "service monitoring zabbix-agent"
   zabbix-agent2,
 # End "service monitoring zabbix-agent"
 # For "service tftp-server"
   tftpd-hpa,
 # End "service tftp-server"
 # For "service dns forwarding"
   pdns-recursor,
 # End "service dns forwarding"
 # For "service sla owamp"
   owamp-client,
   owamp-server,
 # End "service sla owamp"
 # For "service sla twamp"
   twamp-client,
   twamp-server,
 # End "service sla twamp"
 # For "service broadcast-relay"
   udp-broadcast-relay,
 # End "service broadcast-relay"
 # For "high-availability vrrp"
   keepalived (>=2.0.5),
 # End "high-availability-vrrp"
 # For "system console"
   util-linux,
 # End "system console"
 # For "system task-scheduler"
   cron,
 # End "system task-scheduler"
 # For "system lcd"
   lcdproc,
   lcdproc-extra-drivers,
 # End "system lcd"
 # For "system config-management commit-archive"
   git,
 # End "system config-management commit-archive"
 # For firewall
   libndp-tools,
   libnetfilter-conntrack3,
   libnfnetlink0,
   nfct,
   nftables (>= 0.9.3),
 # For "vpn ipsec"
   strongswan (>= 5.9),
   strongswan-swanctl (>= 5.9),
   charon-systemd,
   libcharon-extra-plugins (>=5.9),
   libcharon-extauth-plugins (>=5.9),
   libstrongswan-extra-plugins (>=5.9),
   libstrongswan-standard-plugins (>=5.9),
   python3-vici (>= 5.7.2),
 # End "vpn ipsec"
 # For "nat64"
   jool,
 # End "nat64"
 # For "system conntrack modules rtsp"
   nat-rtsp,
 # End "system conntrack modules rtsp"
 # For "service ntp"
   chrony,
 # End "system ntp"
 # For "vpn openconnect"
   ocserv,
 # End "vpn openconnect"
 # For "system flow-accounting"
   pmacct (>= 1.6.0),
 # End "system flow-accounting"
 # For "system syslog"
   rsyslog,
 # End "system syslog"
 # For "system option keyboard-layout"
   kbd,
 # End "system option keyboard-layout"
 # For "container"
   podman (>=4.9.5),
   netavark,
   aardvark-dns,
 # iptables is only used for containers now, not the the firewall CLI
   iptables,
 # End container
 ## End Configuration mode
 ## Operational mode
 # Used for hypervisor model in "run show version"
   hvinfo,
 # For "run traceroute"
   traceroute,
 # For "run monitor traffic"
   tcpdump,
 # End "run monitor traffic"
 # For "show hardware dmi"
   dmidecode,
 # For "run show hardware storage smart"
   smartmontools,
 # For "run show hardware scsi"
   lsscsi,
 # For "run show hardware pci"
   pciutils,
 # For "show hardware usb"
   usbutils,
 # For "run show hardware storage nvme"
   nvme-cli,
 # For "run monitor bandwidth-test"
   iperf,
   iperf3,
 # End "run monitor bandwidth-test"
 # For "run wake-on-lan"
   etherwake,
 # For "run force ipv6-nd"
   ndisc6,
 # For "run monitor bandwidth"
   bmon,
 # For "run format disk"
   parted,
 # End Operational mode
 ## TPM tools
   cryptsetup,
   tpm2-tools,
 ## End TPM tools
 ## Optional utilities
   easy-rsa,
   tcptraceroute,
   mtr-tiny,
   telnet,
   stunnel4,
   uidmap
 ## End optional utilities
 Description: VyOS configuration scripts and data
  VyOS configuration scripts, interface definitions, and everything
 
 Package: vyos-1x-vmware
 Architecture: amd64
 Depends:
  vyos-1x,
  open-vm-tools
 Description: VyOS configuration scripts and data for VMware
  Adds configuration files required for VyOS running on VMware hosts.
 
 Package: vyos-1x-smoketest
 Architecture: all
 Depends:
  skopeo,
  snmp,
  vyos-1x
 Description: VyOS build sanity checking toolkit
diff --git a/interface-definitions/include/version/reverseproxy-version.xml.i b/interface-definitions/include/version/reverseproxy-version.xml.i
index 907ea1e5e..4f09f2848 100644
--- a/interface-definitions/include/version/reverseproxy-version.xml.i
+++ b/interface-definitions/include/version/reverseproxy-version.xml.i
@@ -1,3 +1,3 @@
 <!-- include start from include/version/reverseproxy-version.xml.i -->
-<syntaxVersion component='reverse-proxy' version='1'></syntaxVersion>
+<syntaxVersion component='reverse-proxy' version='2'></syntaxVersion>
 <!-- include end -->
diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_haproxy.xml.in
similarity index 98%
rename from interface-definitions/load-balancing_reverse-proxy.xml.in
rename to interface-definitions/load-balancing_haproxy.xml.in
index 18274622c..742272436 100644
--- a/interface-definitions/load-balancing_reverse-proxy.xml.in
+++ b/interface-definitions/load-balancing_haproxy.xml.in
@@ -1,344 +1,344 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="load-balancing">
     <children>
-      <node name="reverse-proxy" owner="${vyos_conf_scripts_dir}/load-balancing_reverse-proxy.py">
+      <node name="haproxy" owner="${vyos_conf_scripts_dir}/load-balancing_haproxy.py">
         <properties>
-          <help>Configure reverse-proxy</help>
+          <help>Configure haproxy</help>
           <priority>900</priority>
         </properties>
         <children>
           <tagNode name="service">
             <properties>
               <help>Frontend service name</help>
               <constraint>
                 #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
               </constraint>
               <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
             </properties>
             <children>
               <leafNode name="backend">
                 <properties>
                   <help>Backend member</help>
                   <constraint>
                     #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
                   </constraint>
                   <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
                   <valueHelp>
                     <format>txt</format>
-                    <description>Name of reverse-proxy backend system</description>
+                    <description>Name of haproxy backend system</description>
                   </valueHelp>
                   <completionHelp>
-                    <path>load-balancing reverse-proxy backend</path>
+                    <path>load-balancing haproxy backend</path>
                   </completionHelp>
                   <multi/>
                 </properties>
               </leafNode>
               #include <include/generic-description.xml.i>
               #include <include/listen-address.xml.i>
               #include <include/haproxy/logging.xml.i>
               #include <include/haproxy/mode.xml.i>
               #include <include/port-number.xml.i>
               #include <include/haproxy/rule-frontend.xml.i>
               #include <include/haproxy/tcp-request.xml.i>
               #include <include/haproxy/http-response-headers.xml.i>
               <leafNode name="redirect-http-to-https">
                 <properties>
                   <help>Redirect HTTP to HTTPS</help>
                   <valueless/>
                 </properties>
               </leafNode>
               <node name="ssl">
                 <properties>
                   <help>SSL Certificate, SSL Key and CA</help>
                 </properties>
                 <children>
                   #include <include/pki/certificate-multi.xml.i>
                 </children>
               </node>
             </children>
           </tagNode>
           <tagNode name="backend">
             <properties>
               <help>Backend server name</help>
               <constraint>
                 #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
               </constraint>
               <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
             </properties>
             <children>
               <leafNode name="balance">
                 <properties>
                   <help>Load-balancing algorithm</help>
                   <completionHelp>
                     <list>source-address round-robin least-connection</list>
                   </completionHelp>
                   <valueHelp>
                     <format>source-address</format>
                     <description>Based on hash of source IP address</description>
                   </valueHelp>
                   <valueHelp>
                     <format>round-robin</format>
                     <description>Round robin</description>
                   </valueHelp>
                   <valueHelp>
                     <format>least-connection</format>
                     <description>Least connection</description>
                   </valueHelp>
                   <constraint>
                     <regex>(source-address|round-robin|least-connection)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>round-robin</defaultValue>
               </leafNode>
               #include <include/generic-description.xml.i>
               #include <include/haproxy/logging.xml.i>
               #include <include/haproxy/mode.xml.i>
               #include <include/haproxy/http-response-headers.xml.i>
               <node name="http-check">
                 <properties>
                   <help>HTTP check configuration</help>
                 </properties>
                 <children>
                   <leafNode name="method">
                     <properties>
                       <help>HTTP method used for health check</help>
                       <completionHelp>
                         <list>options head get post put</list>
                       </completionHelp>
                       <valueHelp>
                         <format>options|head|get|post|put</format>
                         <description>HTTP method used for health checking</description>
                       </valueHelp>
                       <constraint>
                         <regex>(options|head|get|post|put)</regex>
                       </constraint>
                     </properties>
                   </leafNode>
                   <leafNode name="uri">
                     <properties>
                       <help>URI used for HTTP health check (Example: '/' or '/health')</help>
                       <constraint>
                         <regex>^\/([^?#\s]*)(\?[^#\s]*)?$</regex>
                       </constraint>
                     </properties>
                   </leafNode>
                   <node name="expect">
                     <properties>
                       <help>Expected response for the health check to pass</help>
                     </properties>
                     <children>
                       <leafNode name="status">
                         <properties>
                           <help>Expected response status code for the health check to pass</help>
                           <valueHelp>
                             <format>u32:200-399</format>
                             <description>Expected response code</description>
                           </valueHelp>
                           <constraint>
                             <validator name="numeric" argument="--range 200-399"/>
                           </constraint>
                           <constraintErrorMessage>Status code must be in range 200-399</constraintErrorMessage>
                         </properties>
                       </leafNode>
                       <leafNode name="string">
                         <properties>
                           <help>Expected to be in response body for the health check to pass</help>
                           <valueHelp>
                             <format>txt</format>
                             <description>A string expected to be in the response</description>
                           </valueHelp>
                         </properties>
                       </leafNode>
                     </children>
                   </node>
                 </children>
               </node>
               <leafNode name="health-check">
                 <properties>
                   <help>Non HTTP health check options</help>
                   <completionHelp>
                     <list>ldap mysql pgsql redis smtp</list>
                   </completionHelp>
                   <valueHelp>
                     <format>ldap</format>
                     <description>LDAP protocol check</description>
                   </valueHelp>
                   <valueHelp>
                     <format>mysql</format>
                     <description>MySQL protocol check</description>
                   </valueHelp>
                   <valueHelp>
                     <format>pgsql</format>
                     <description>PostgreSQL protocol check</description>
                   </valueHelp>
                   <valueHelp>
                     <format>redis</format>
                     <description>Redis protocol check</description>
                   </valueHelp>
                   <valueHelp>
                     <format>smtp</format>
                     <description>SMTP protocol check</description>
                   </valueHelp>
                   <constraint>
                     <regex>(ldap|mysql|redis|pgsql|smtp)</regex>
                   </constraint>
                 </properties>
               </leafNode>
               #include <include/haproxy/rule-backend.xml.i>
               <tagNode name="server">
                 <properties>
                   <help>Backend server name</help>
                 </properties>
                 <children>
                   <leafNode name="address">
                     <properties>
                       <help>Backend server address</help>
                       <valueHelp>
                         <format>ipv4</format>
                         <description>IPv4 unicast peer address</description>
                       </valueHelp>
                       <valueHelp>
                         <format>ipv6</format>
                         <description>IPv6 unicast peer address</description>
                       </valueHelp>
                       <constraint>
                         <validator name="ip-address"/>
                       </constraint>
                     </properties>
                   </leafNode>
                   <leafNode name="backup">
                     <properties>
                       <help>Use backup server if other servers are not available</help>
                       <valueless/>
                     </properties>
                   </leafNode>
                   <leafNode name="check">
                     <properties>
                       <help>Active health check backend server</help>
                       <valueless/>
                     </properties>
                   </leafNode>
                   #include <include/port-number.xml.i>
                   <leafNode name="send-proxy">
                     <properties>
                       <help>Send a Proxy Protocol version 1 header (text format)</help>
                       <valueless/>
                     </properties>
                   </leafNode>
                   <leafNode name="send-proxy-v2">
                     <properties>
                       <help>Send a Proxy Protocol version 2 header (binary format)</help>
                       <valueless/>
                     </properties>
                   </leafNode>
                 </children>
               </tagNode>
               <node name="ssl">
                 <properties>
                   <help>SSL Certificate, SSL Key and CA</help>
                 </properties>
                 <children>
                   #include <include/pki/ca-certificate.xml.i>
                   <leafNode name="no-verify">
                     <properties>
                       <help>Do not attempt to verify SSL certificates for backend servers</help>
                       <valueless/>
                     </properties>
                   </leafNode>
                 </children>
               </node>
               #include <include/haproxy/timeout.xml.i>
             </children>
           </tagNode>
           <node name="global-parameters">
             <properties>
               <help>Global perfomance parameters and limits</help>
             </properties>
             <children>
               #include <include/haproxy/logging.xml.i>
               <leafNode name="max-connections">
                 <properties>
                   <help>Maximum allowed connections</help>
                   <valueHelp>
                     <format>u32:1-2000000</format>
                     <description>Maximum allowed connections</description>
                   </valueHelp>
                   <constraint>
                     <validator name="numeric" argument="--range 1-2000000"/>
                   </constraint>
                 </properties>
               </leafNode>
               <leafNode name="ssl-bind-ciphers">
                 <properties>
                   <help>Cipher algorithms ("cipher suite") used during SSL/TLS handshake for all frontend servers</help>
                   <completionHelp>
                     <list>ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384</list>
                   </completionHelp>
                   <valueHelp>
                     <format>ecdhe-ecdsa-aes128-gcm-sha256</format>
                     <description>ecdhe-ecdsa-aes128-gcm-sha256</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ecdhe-rsa-aes128-gcm-sha256</format>
                     <description>ecdhe-rsa-aes128-gcm-sha256</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ecdhe-ecdsa-aes256-gcm-sha384</format>
                     <description>ecdhe-ecdsa-aes256-gcm-sha384</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ecdhe-rsa-aes256-gcm-sha384</format>
                     <description>ecdhe-rsa-aes256-gcm-sha384</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ecdhe-ecdsa-chacha20-poly1305</format>
                     <description>ecdhe-ecdsa-chacha20-poly1305</description>
                   </valueHelp>
                   <valueHelp>
                     <format>ecdhe-rsa-chacha20-poly1305</format>
                     <description>ecdhe-rsa-chacha20-poly1305</description>
                   </valueHelp>
                   <valueHelp>
                     <format>dhe-rsa-aes128-gcm-sha256</format>
                     <description>dhe-rsa-aes128-gcm-sha256</description>
                   </valueHelp>
                   <valueHelp>
                     <format>dhe-rsa-aes256-gcm-sha384</format>
                     <description>dhe-rsa-aes256-gcm-sha384</description>
                   </valueHelp>
                   <constraint>
                     <regex>(ecdhe-ecdsa-aes128-gcm-sha256|ecdhe-rsa-aes128-gcm-sha256|ecdhe-ecdsa-aes256-gcm-sha384|ecdhe-rsa-aes256-gcm-sha384|ecdhe-ecdsa-chacha20-poly1305|ecdhe-rsa-chacha20-poly1305|dhe-rsa-aes128-gcm-sha256|dhe-rsa-aes256-gcm-sha384)</regex>
                   </constraint>
                   <multi/>
                 </properties>
                 <defaultValue>ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384</defaultValue>
               </leafNode>
               <leafNode name="tls-version-min">
                 <properties>
                   <help>Specify the minimum required TLS version</help>
                   <completionHelp>
                     <list>1.2 1.3</list>
                   </completionHelp>
                   <valueHelp>
                     <format>1.2</format>
                     <description>TLS v1.2</description>
                   </valueHelp>
                   <valueHelp>
                     <format>1.3</format>
                     <description>TLS v1.3</description>
                   </valueHelp>
                   <constraint>
                     <regex>(1.2|1.3)</regex>
                   </constraint>
                 </properties>
                 <defaultValue>1.3</defaultValue>
               </leafNode>
             </children>
           </node>
           #include <include/interface/vrf.xml.i>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/op-mode-definitions/reverse-proxy.xml.in b/op-mode-definitions/load-balacing_haproxy.in
similarity index 55%
rename from op-mode-definitions/reverse-proxy.xml.in
rename to op-mode-definitions/load-balacing_haproxy.in
index b45ce107f..c3d6c799b 100644
--- a/op-mode-definitions/reverse-proxy.xml.in
+++ b/op-mode-definitions/load-balacing_haproxy.in
@@ -1,23 +1,23 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="restart">
     <children>
-      <node name="reverse-proxy">
+      <node name="haproxy">
         <properties>
-          <help>Restart reverse-proxy service</help>
+          <help>Restart haproxy service</help>
         </properties>
-        <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name reverse_proxy</command>
+        <command>sudo ${vyos_op_scripts_dir}/restart.py restart_service --name haproxy</command>
       </node>
     </children>
   </node>
   <node name="show">
     <children>
-      <node name="reverse-proxy">
+      <node name="haproxy">
         <properties>
-          <help>Show load-balancing reverse-proxy</help>
+          <help>Show load-balancing haproxy</help>
         </properties>
-        <command>sudo ${vyos_op_scripts_dir}/reverseproxy.py show</command>
+        <command>sudo ${vyos_op_scripts_dir}/load-balacing_haproxy.py show</command>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_haproxy.py
similarity index 99%
rename from smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
rename to smoketest/scripts/cli/test_load-balancing_haproxy.py
index 34f77b95d..967eb3869 100755
--- a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
+++ b/smoketest/scripts/cli/test_load-balancing_haproxy.py
@@ -1,502 +1,502 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import unittest
 
 from base_vyostest_shim import VyOSUnitTestSHIM
 
 from vyos.configsession import ConfigSessionError
 from vyos.utils.process import process_named_running
 from vyos.utils.file import read_file
 
 PROCESS_NAME = 'haproxy'
 HAPROXY_CONF = '/run/haproxy/haproxy.cfg'
-base_path = ['load-balancing', 'reverse-proxy']
+base_path = ['load-balancing', 'haproxy']
 proxy_interface = 'eth1'
 
 valid_ca_cert = """
 MIIDnTCCAoWgAwIBAgIUewSDtLiZbhg1YEslMnqRl1shoPcwDQYJKoZIhvcNAQEL
 BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM
 CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0y
 NDA0MDEwNTQ3MzJaFw0yOTAzMzEwNTQ3MzJaMFcxCzAJBgNVBAYTAkdCMRMwEQYD
 VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5
 T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
 AoIBAQC/D6W27rfpdPIf16JHs8fx/7VehyCk8m03dPAQqv6wQiHF5xhXaFZER1+c
 nf7oExp9zi/4HJ/KRbcc1loVArXtV0zwAUftBmUeezGVfxhCHKhP89GnV4NB97jj
 klHFSxjEoT/0YvJQ1IV/3Cos1T5O8x14WIi31l7WQGYAyWxUXiP8QxGVmF3odEJo
 O3e7Ew9HFkamvuL6Z6c4uAVMM7uYXme7q0OM49Wu7C9hj39ZKbjG5FFKZTj+zDKg
 SbOiQaFk3blOky/e3ifNjZelGtussYPOMBkUirLvrSGGy7s3lm8Yp5PH5+UkVQB2
 rZyxRdZTC9kh+dShR1s/qcPnDw7lAgMBAAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8w
 DgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAd
 BgNVHQ4EFgQU/HE2UPn8JQB/9EL52GquPxZqr5MwDQYJKoZIhvcNAQELBQADggEB
 AIkMmqyoMqidTa3lvUPJNl4H+Ef/yPQkTkrsOd3WL8DQysyUdMLdQozr3K1bH5XB
 wRxoXX211nu4WhN18LsFJRCuHBSxmaNkBGFyl+JNvhPUSI6j0somNMCS75KJ0ZDx
 2HZsXmmJFF902VQxCR7vCIrFDrKDYq1e7GQbFS8t46FlpqivQMQWNPt18Bthj/1Y
 lO2GKRWFCX8VlOW7FtDQ6B3oC1oAGHBBGogAx7/0gh9DnYBKT14V/kuWW3RNABZJ
 ewHO1C6icQdnjtaREDyTP4oyL+uyAfXrFfbpti2hc00f8oYPQZYxj1yxl4UAdNij
 mS6YqH/WRioGMe3tBVeSdoo=
 """
 
 valid_ca_private_key = """
 MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/D6W27rfpdPIf
 16JHs8fx/7VehyCk8m03dPAQqv6wQiHF5xhXaFZER1+cnf7oExp9zi/4HJ/KRbcc
 1loVArXtV0zwAUftBmUeezGVfxhCHKhP89GnV4NB97jjklHFSxjEoT/0YvJQ1IV/
 3Cos1T5O8x14WIi31l7WQGYAyWxUXiP8QxGVmF3odEJoO3e7Ew9HFkamvuL6Z6c4
 uAVMM7uYXme7q0OM49Wu7C9hj39ZKbjG5FFKZTj+zDKgSbOiQaFk3blOky/e3ifN
 jZelGtussYPOMBkUirLvrSGGy7s3lm8Yp5PH5+UkVQB2rZyxRdZTC9kh+dShR1s/
 qcPnDw7lAgMBAAECggEAGm+j0kf9koPn7Jf9kEZD6CwlgEraLXiNvBqmDOhcDS9Z
 VPTA3XdGWHQ3uofx+VKLW9TntkDfqzEyQP83v6h8W7a0opDKzvUPkMQi/Dh1ttAY
 SdfGrozhUINiRbq9LbtSVgKpwrreJGkDf8mK3GE1Gd9xuHEnmahDvwlyE7HLF3Eh
 2xJDSAPx3OxcjR5hW7vbojhVCyCfuYTlZB86f0Sb8SqxZMt/y2zKmbzoTqpUBWbg
 lBnE7GJoNR07DWjxvEP8r6kQMh670I01SUR42CSK8X8asHhhZHUcggsNno+BBc6K
 sy4HzDIYIay6oy0atcVzKsGrlNCveeAiSEcw7x2yAQKBgQDsXz2FbhXYV5Vbt4wU
 5EWOa7if/+FG+TcVezOF3xlNBgykjXHQaYTYHrJq0qsEFrNT3ZGm9ezY4LdF3BTt
 5z/+i8QlCCw/nr3N7JZx6U5+OJl1j3NLFoFx3+DXo31pgJJEQCHHwdCkF5IuOcZ/
 b3nXkRZ80BVv7XD6F9bMHEwLYQKBgQDO7THcRDbsE6/+7VsTDf0P/JENba3DBBu1
 gjb1ItL5FHJwMgnkUadRZRo0QKye848ugribed39qSoJfNaBJrAT5T8S/9q+lXft
 vXUckcBO1CKNaP9gqF5fPIdNHf64GbmCiiHjOTE3rwJjkxJPpzLXyvgBO4aLeesK
 ThBdW+iWBQKBgD3crz08knsMcQqP/xl4pLuhdbBqR4tLrh7xH4rp2LVP3/8xBZiG
 BT6Kyicq+5cWWdiZJIWN127rYQvnjZK18wmriqomeW4tHX/Ha5hkdyaRqZga8xGz
 0iz7at0E7M2v2JgEMNMW5oQLpzZx6IFxq3G/hyMjUnj4q5jIpG7G+SABAoGBAKgT
 8Ika+4WcpDssrup2VVTT8Tp4GUkroBo6D8vkInvhiObrLi+/x2mM9tD0q4JdEbNU
 yQC454EwFA4q0c2MED/I2QfkvNhLbmO0nVi8ZvlgxEQawjzP5f/zmW8haxI9Cvsm
 mkoH3Zt+UzFwd9ItXFX97p6JrErEmA8Bw7chfXXFAoGACWR/c+s7hnX6gzyah3N1
 Db0xAaS6M9fzogcg2OM1i/6OCOcp4Sh1fmPG7tN45CCnFkhgVoRkSSA5MJAe2I/r
 xFm72VX7567T+4qIFua2iDxIBA/Z4zmj+RYfhHGPYZjdSjprKJxY6QOv5aoluBvE
 mlLy1Hmcry+ukWZtWezZfGY=
 """
 
 valid_cert = """
 MIIDsTCCApmgAwIBAgIUDKOfYIwwtjww0vAMvJnXnGLhL+0wDQYJKoZIhvcNAQEL
 BQAwVzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM
 CVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzEQMA4GA1UEAwwHdnlvcy5pbzAeFw0y
 NDA0MDEwNTQ5NTdaFw0yNTA0MDEwNTQ5NTdaMFcxCzAJBgNVBAYTAkdCMRMwEQYD
 VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlTb21lLUNpdHkxDTALBgNVBAoMBFZ5
 T1MxEDAOBgNVBAMMB3Z5b3MuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
 AoIBAQCHtW25Umt6rqm2gfzqAZg1/VsqefZwAqIUAm2T3VwHQZ/2tNdr8ROWASii
 W5PToC7N8StMwFl2YoIof+MXGMO00toTTJePZOJKjF9U9hL3kuYuY1+yng4fl+E0
 96xVobb2KY4lMZ2rVwmpB7jkNO2LWxbJ6vHKcwMOhlx/8NEKIoVmkBT1Zkgy5dgn
 PgTtJcdVIU75XhQWqBmAUsMmACuZfqSYJbAv3hHz5V+Ejt0dI6mlGM7TXsCC9tKM
 64paIKZooFm78IsxJ26jHpZ8eh+SDBz0VBydBFWXm8VhOJ8NlZ1opAh3AWxFZDGt
 49uOsy82VmUcHPyoZ8DKYkBFHfSpAgMBAAGjdTBzMAwGA1UdEwEB/wQCMAAwDgYD
 VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB0GA1UdDgQWBBTeTcgM
 pRxAMjVBirjzo2QUu5H5fzAfBgNVHSMEGDAWgBT8cTZQ+fwlAH/0QvnYaq4/Fmqv
 kzANBgkqhkiG9w0BAQsFAAOCAQEAi4dBcH7TIYwWRW6bWRubMA7ztonV4EYb15Zf
 9yNafMWAEEBOii/DFo+j/ky9oInl7ZHw7gTIyXfLEarX/bM6fHOgiyj4zp3u6RnH
 5qlBypu/YCnyPjE/GvV05m2rrXnxZ4rCtcoO4u/HyGbV+jGnCmjShKICKyu1FdMd
 eeZRrLKPO/yghadGH34WVQnrbaorwlbi+NjB6fxmZQx5HE/SyK/9sb6WCpLMGHoy
 MpdQo3lV1ewtL3ElIWDq6mO030Mo5pwpjIU+8yHHNBVzg6mlGVgQPAp0gbUei9aP
 CJ8SLmMEi3NDk0E/sPgVC17e6bf2bx2nRuXROZekG2dd90Iu8g==
 """
 
 valid_cert_private_key = """
 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCHtW25Umt6rqm2
 gfzqAZg1/VsqefZwAqIUAm2T3VwHQZ/2tNdr8ROWASiiW5PToC7N8StMwFl2YoIo
 f+MXGMO00toTTJePZOJKjF9U9hL3kuYuY1+yng4fl+E096xVobb2KY4lMZ2rVwmp
 B7jkNO2LWxbJ6vHKcwMOhlx/8NEKIoVmkBT1Zkgy5dgnPgTtJcdVIU75XhQWqBmA
 UsMmACuZfqSYJbAv3hHz5V+Ejt0dI6mlGM7TXsCC9tKM64paIKZooFm78IsxJ26j
 HpZ8eh+SDBz0VBydBFWXm8VhOJ8NlZ1opAh3AWxFZDGt49uOsy82VmUcHPyoZ8DK
 YkBFHfSpAgMBAAECggEABofhw0W/ACEMcAjmpNTFkFCUXPGQXWDVD7EzuIZSNdOv
 yOm4Rbys6H6/B7wwO6KVagoBf1Cw5Xh1YtFPuoZxsZ+liMD6eLc+SB/j/RTYAhPO
 0bvsyK3gSF8w4nGKWLce9M74ZRwThkG6qGijmlDdPyP3r2kn8GoTQzVOWYZbavk/
 H3uE6PsZSWjOY+Mnm3vEmeItPYKGZ5+IP+YiTqZ4NCggBwH7csnR3/kbwY5Ns7jl
 3Av+EAdIeUwDNeMfLTzN7GphJR7gL6YQIhGKxE+W0GHXL2FubnnrFx8G75HFh1ay
 GkJXEqY5Lbd+7VPS0KcQdwhMSSoJsY5GUORUqrU80QKBgQC/0wJSu+Gfe7dONIby
 mnGRppSRIQVRjCjbVIN+Y2h1Kp3aK0qDpV7KFLCiUUtz9rWHR/NB4cDaIW543T55
 /jXUMD2j3EqtbtlsVQfDLQV7DyDrMmBAs4REHmyZmWTzHjCDUO79ahdOlZs34Alz
 wfpX3L3WVYGIAJKZtsUZ8FbrGQKBgQC1HFgVZ1PqP9/pW50RMh06BbQrhWPGiWgH
 Rn5bFthLkp3uqr9bReBq9tu3sqJuAhFudH68wup+Z+fTcHAcNg2Rs+Q+IKnULdB/
 UQHYoPjeWOvHAuOmgn9iD9OD7GCIv8fZmLit09vAsOWq+NKNBKCknGM70CDrvAlQ
 lOAUa34YEQKBgQC5i8GThWiYe3Kzktt1jy6LVDYgq3AZkRl0Diui9UT1EGPfxEAv
 VqZ5kcnJOBlj8h9k25PRBi0k0XGqN1dXaS1oMcFt3ofdenuU7iqz/7htcBTHa9Lu
 wrYNreAeMuISyADlBEQnm5cvzEZ3pZ1++wLMOhjmWY8Rnnwvczrz/CYXAQKBgH+t
 vcNJFvWblkUzWuWWiNgw0TWlUhPTJs2KOuYIku+kK0bohQLZnj6KTZeRjcU0HAnc
 gsScPShkJCEBsWeSC7reMVhDOrbknYpEF6MayJgn5ABm3wqyEQ+WzKzCZcPCQCf8
 7KVPKCsOCrufsv/LdVzXC3ZNYggOhhqS+e4rYbehAoGBAIsq252o3vgrunzS5FZx
 IONA2FvYrxVbDn5aF8WfNSdKFy3CAlt0P+Fm8gYbrKylIfMXpL8Oqc9RJou5onZP
 ZXLrtgVJR9W020qTurO2f91qfU8646n11hR9ObBB1IYbagOU0Pw1Nrq/FRp/u2tx
 7i7xFz2WEiQeSCPaKYOiqM3t
 """
 
 
 class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase):
     def tearDown(self):
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
         self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address'])
         self.cli_delete(base_path)
         self.cli_delete(['pki'])
         self.cli_commit()
 
         # Process must be terminated after deleting the config
         self.assertFalse(process_named_running(PROCESS_NAME))
 
     def base_config(self):
         self.cli_set(base_path + ['service', 'https_front', 'mode', 'http'])
         self.cli_set(base_path + ['service', 'https_front', 'port', '4433'])
         self.cli_set(base_path + ['service', 'https_front', 'backend', 'bk-01'])
 
         self.cli_set(base_path + ['backend', 'bk-01', 'mode', 'http'])
         self.cli_set(base_path + ['backend', 'bk-01', 'server', 'bk-01', 'address', '192.0.2.11'])
         self.cli_set(base_path + ['backend', 'bk-01', 'server', 'bk-01', 'port', '9090'])
         self.cli_set(base_path + ['backend', 'bk-01', 'server', 'bk-01', 'send-proxy'])
 
         self.cli_set(base_path + ['global-parameters', 'max-connections', '1000'])
 
     def configure_pki(self):
 
         # Valid CA
         self.cli_set(['pki', 'ca', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')])
         self.cli_set(['pki', 'ca', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')])
 
         # Valid cert
         self.cli_set(['pki', 'certificate', 'smoketest', 'certificate', valid_cert.replace('\n','')])
         self.cli_set(['pki', 'certificate', 'smoketest', 'private', 'key', valid_cert_private_key.replace('\n','')])
 
     def test_01_lb_reverse_proxy_domain(self):
         domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com']
         domain_bk_second = 'n5.example.com'
         frontend = 'https_front'
         front_port = '4433'
         bk_server_first = '192.0.2.11'
         bk_server_second = '192.0.2.12'
         bk_first_name = 'bk-01'
         bk_second_name = 'bk-02'
         bk_server_port = '9090'
         mode = 'http'
         rule_ten = '10'
         rule_twenty = '20'
         rule_thirty = '30'
         send_proxy = 'send-proxy'
         max_connections = '1000'
 
         back_base = base_path + ['backend']
 
         self.cli_set(base_path + ['service', frontend, 'mode', mode])
         self.cli_set(base_path + ['service', frontend, 'port', front_port])
         for domain in domains_bk_first:
             self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'url-path', 'end', '/test'])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'set', 'backend', bk_second_name])
 
         self.cli_set(back_base + [bk_first_name, 'mode', mode])
         self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first])
         self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port])
         self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy])
 
         self.cli_set(back_base + [bk_second_name, 'mode', mode])
         self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second])
         self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port])
         self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'backup'])
 
         self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections])
 
         # commit changes
         self.cli_commit()
 
         config = read_file(HAPROXY_CONF)
 
         # Global
         self.assertIn(f'maxconn {max_connections}', config)
 
         # Frontend
         self.assertIn(f'frontend {frontend}', config)
         self.assertIn(f'bind [::]:{front_port} v4v6', config)
         self.assertIn(f'mode {mode}', config)
         for domain in domains_bk_first:
             self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config)
         self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config)
         self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config)
         self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config)
         self.assertIn(f'acl {rule_thirty} path -i -m end /test', config)
         self.assertIn(f'use_backend {bk_second_name} if {rule_thirty}', config)
 
         # Backend
         self.assertIn(f'backend {bk_first_name}', config)
         self.assertIn(f'balance roundrobin', config)
         self.assertIn(f'option forwardfor', config)
         self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config)
         self.assertIn(f'mode {mode}', config)
         self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config)
 
         self.assertIn(f'backend {bk_second_name}', config)
         self.assertIn(f'mode {mode}', config)
         self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config)
         self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port} backup', config)
 
     def test_02_lb_reverse_proxy_cert_not_exists(self):
         self.base_config()
         self.cli_set(base_path + ['service', 'https_front', 'ssl', 'certificate', 'cert'])
 
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
         # self.assertIn('\nCertificates does not exist in PKI\n', str(e.exception))
 
         self.cli_delete(base_path)
         self.configure_pki()
 
         self.base_config()
         self.cli_set(base_path + ['service', 'https_front', 'ssl', 'certificate', 'cert'])
 
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
         # self.assertIn('\nCertificate "cert" does not exist\n', str(e.exception))
 
         self.cli_delete(base_path + ['service', 'https_front', 'ssl', 'certificate', 'cert'])
         self.cli_set(base_path + ['service', 'https_front', 'ssl', 'certificate', 'smoketest'])
         self.cli_commit()
 
     def test_03_lb_reverse_proxy_ca_not_exists(self):
         self.base_config()
         self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'ca-test'])
 
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
         # self.assertIn('\nCA certificates does not exist in PKI\n', str(e.exception))
 
         self.cli_delete(base_path)
         self.configure_pki()
 
         self.base_config()
         self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'ca-test'])
 
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
         # self.assertIn('\nCA certificate "ca-test" does not exist\n', str(e.exception))
 
         self.cli_delete(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'ca-test'])
         self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'smoketest'])
         self.cli_commit()
 
     def test_04_lb_reverse_proxy_backend_ssl_no_verify(self):
         # Setup base
         self.configure_pki()
         self.base_config()
 
         # Set no-verify option
         self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'no-verify'])
         self.cli_commit()
 
         # Test no-verify option
         config = read_file(HAPROXY_CONF)
         self.assertIn('server bk-01 192.0.2.11:9090 send-proxy ssl verify none', config)
 
         # Test setting ca-certificate alongside no-verify option fails, to test config validation
         self.cli_set(base_path + ['backend', 'bk-01', 'ssl', 'ca-certificate', 'smoketest'])
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
 
     def test_05_lb_reverse_proxy_backend_http_check(self):
         # Setup base
         self.base_config()
 
         # Set http-check
         self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'method', 'get'])
         self.cli_commit()
 
         # Test http-check
         config = read_file(HAPROXY_CONF)
         self.assertIn('option httpchk', config)
         self.assertIn('http-check send meth GET', config)
 
         # Set http-check with uri and status
         self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'uri', '/health'])
         self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'expect', 'status', '200'])
         self.cli_commit()
 
         # Test http-check with uri and status
         config = read_file(HAPROXY_CONF)
         self.assertIn('option httpchk', config)
         self.assertIn('http-check send meth GET uri /health', config)
         self.assertIn('http-check expect status 200', config)
 
         # Set http-check with string
         self.cli_delete(base_path + ['backend', 'bk-01', 'http-check', 'expect', 'status', '200'])
         self.cli_set(base_path + ['backend', 'bk-01', 'http-check', 'expect', 'string', 'success'])
         self.cli_commit()
 
         # Test http-check with string
         config = read_file(HAPROXY_CONF)
         self.assertIn('option httpchk', config)
         self.assertIn('http-check send meth GET uri /health', config)
         self.assertIn('http-check expect string success', config)
 
         # Test configuring both http-check & health-check fails validation script
         self.cli_set(base_path + ['backend', 'bk-01', 'health-check', 'ldap'])
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
 
     def test_06_lb_reverse_proxy_tcp_mode(self):
         frontend = 'tcp_8443'
         mode = 'tcp'
         front_port = '8433'
         tcp_request_delay = "5000"
         rule_thirty = '30'
         domain_bk = 'n6.example.com'
         ssl_opt = "req-ssl-sni"
         bk_name = 'bk-03'
         bk_server = '192.0.2.11'
         bk_server_port = '9090'
 
         back_base = base_path + ['backend']
 
         self.cli_set(base_path + ['service', frontend, 'mode', mode])
         self.cli_set(base_path + ['service', frontend, 'port', front_port])
         self.cli_set(base_path + ['service', frontend, 'tcp-request', 'inspect-delay', tcp_request_delay])
 
         self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'domain-name', domain_bk])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'ssl', ssl_opt])
         self.cli_set(base_path + ['service', frontend, 'rule', rule_thirty, 'set', 'backend', bk_name])
 
         self.cli_set(back_base + [bk_name, 'mode', mode])
         self.cli_set(back_base + [bk_name, 'server', bk_name, 'address', bk_server])
         self.cli_set(back_base + [bk_name, 'server', bk_name, 'port', bk_server_port])
 
         # commit changes
         self.cli_commit()
 
         config = read_file(HAPROXY_CONF)
 
         # Frontend
         self.assertIn(f'frontend {frontend}', config)
         self.assertIn(f'bind [::]:{front_port} v4v6', config)
         self.assertIn(f'mode {mode}', config)
 
         self.assertIn(f'tcp-request inspect-delay {tcp_request_delay}', config)
         self.assertIn(f"tcp-request content accept if {{ req_ssl_hello_type 1 }}", config)
         self.assertIn(f'acl {rule_thirty} req_ssl_sni -i {domain_bk}', config)
         self.assertIn(f'use_backend {bk_name} if {rule_thirty}', config)
 
         # Backend
         self.assertIn(f'backend {bk_name}', config)
         self.assertIn(f'balance roundrobin', config)
         self.assertIn(f'mode {mode}', config)
         self.assertIn(f'server {bk_name} {bk_server}:{bk_server_port}', config)
 
     def test_07_lb_reverse_proxy_http_response_headers(self):
         # Setup base
         self.configure_pki()
         self.base_config()
 
         # Set example headers in both frontend and backend
         self.cli_set(base_path + ['service', 'https_front', 'http-response-headers', 'Cache-Control', 'value', 'max-age=604800'])
         self.cli_set(base_path + ['backend', 'bk-01',  'http-response-headers', 'Proxy-Backend-ID', 'value', 'bk-01'])
         self.cli_commit()
 
         # Test headers are present in generated configuration file
         config = read_file(HAPROXY_CONF)
         self.assertIn('http-response set-header Cache-Control \'max-age=604800\'', config)
         self.assertIn('http-response set-header Proxy-Backend-ID \'bk-01\'', config)
 
         # Test setting alongside modes other than http is blocked by validation conditions
         self.cli_set(base_path + ['service', 'https_front', 'mode', 'tcp'])
         with self.assertRaises(ConfigSessionError) as e:
             self.cli_commit()
 
     def test_08_lb_reverse_proxy_tcp_health_checks(self):
         # Setup PKI
         self.configure_pki()
 
         # Define variables
         frontend = 'fe_ldaps'
         mode = 'tcp'
         health_check = 'ldap'
         front_port = '636'
         bk_name = 'bk_ldap'
         bk_servers = ['192.0.2.11', '192.0.2.12']
         bk_server_port = '389'
 
         # Configure frontend
         self.cli_set(base_path + ['service', frontend, 'mode', mode])
         self.cli_set(base_path + ['service', frontend, 'port', front_port])
         self.cli_set(base_path + ['service', frontend, 'ssl', 'certificate', 'smoketest'])
 
         # Configure backend
         self.cli_set(base_path + ['backend', bk_name, 'mode', mode])
         self.cli_set(base_path + ['backend', bk_name, 'health-check', health_check])
         for index, bk_server in enumerate(bk_servers):
             self.cli_set(base_path + ['backend', bk_name, 'server', f'srv-{index}', 'address', bk_server])
             self.cli_set(base_path + ['backend', bk_name, 'server', f'srv-{index}', 'port', bk_server_port])
 
         # Commit & read config
         self.cli_commit()
         config = read_file(HAPROXY_CONF)
 
         # Validate Frontend
         self.assertIn(f'frontend {frontend}', config)
         self.assertIn(f'bind [::]:{front_port} v4v6 ssl crt /run/haproxy/smoketest.pem', config)
         self.assertIn(f'mode {mode}', config)
         self.assertIn(f'backend {bk_name}', config)
 
         # Validate Backend
         self.assertIn(f'backend {bk_name}', config)
         self.assertIn(f'option {health_check}-check', config)
         self.assertIn(f'mode {mode}', config)
         for index, bk_server in enumerate(bk_servers):
             self.assertIn(f'server srv-{index} {bk_server}:{bk_server_port}', config)
 
         # Validate SMTP option renders correctly
         self.cli_set(base_path + ['backend', bk_name, 'health-check', 'smtp'])
         self.cli_commit()
         config = read_file(HAPROXY_CONF)
         self.assertIn(f'option smtpchk', config)
 
     def test_09_lb_reverse_proxy_logging(self):
         # Setup base
         self.base_config()
         self.cli_commit()
 
         # Ensure default logging configuration is present
         config = read_file(HAPROXY_CONF)
 
         # Test global-parameters logging options
         self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local1', 'level', 'err'])
         self.cli_set(base_path + ['global-parameters', 'logging', 'facility', 'local2', 'level', 'warning'])
         self.cli_commit()
 
         # Test global logging parameters are generated in configuration file
         config = read_file(HAPROXY_CONF)
         self.assertIn('log /dev/log local1 err', config)
         self.assertIn('log /dev/log local2 warning', config)
 
         # Test backend logging options
         backend_path = base_path + ['backend', 'bk-01']
         self.cli_set(backend_path + ['logging', 'facility', 'local3', 'level', 'debug'])
         self.cli_set(backend_path + ['logging', 'facility', 'local4', 'level', 'info'])
         self.cli_commit()
 
         # Test backend logging parameters are generated in configuration file
         config = read_file(HAPROXY_CONF)
         self.assertIn('log /dev/log local3 debug', config)
         self.assertIn('log /dev/log local4 info', config)
 
         # Test service logging options
         service_path = base_path + ['service', 'https_front']
         self.cli_set(service_path + ['logging', 'facility', 'local5', 'level', 'notice'])
         self.cli_set(service_path + ['logging', 'facility', 'local6', 'level', 'crit'])
         self.cli_commit()
 
         # Test service logging parameters are generated in configuration file
         config = read_file(HAPROXY_CONF)
         self.assertIn('log /dev/log local5 notice', config)
         self.assertIn('log /dev/log local6 crit', config)
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_haproxy.py
old mode 100755
new mode 100644
similarity index 99%
rename from src/conf_mode/load-balancing_reverse-proxy.py
rename to src/conf_mode/load-balancing_haproxy.py
index 17226efe9..45042dd52
--- a/src/conf_mode/load-balancing_reverse-proxy.py
+++ b/src/conf_mode/load-balancing_haproxy.py
@@ -1,206 +1,206 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import exit
 from shutil import rmtree
 
 from vyos.config import Config
 from vyos.configverify import verify_pki_certificate
 from vyos.configverify import verify_pki_ca_certificate
 from vyos.utils.dict import dict_search
 from vyos.utils.process import call
 from vyos.utils.network import check_port_availability
 from vyos.utils.network import is_listen_port_bind_service
 from vyos.pki import find_chain
 from vyos.pki import load_certificate
 from vyos.pki import load_private_key
 from vyos.pki import encode_certificate
 from vyos.pki import encode_private_key
 from vyos.template import render
 from vyos.utils.file import write_file
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 load_balancing_dir = '/run/haproxy'
 load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg'
 systemd_service = 'haproxy.service'
 systemd_override = '/run/systemd/system/haproxy.service.d/10-override.conf'
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
 
-    base = ['load-balancing', 'reverse-proxy']
+    base = ['load-balancing', 'haproxy']
     if not conf.exists(base):
         return None
     lb = conf.get_config_dict(base,
                               get_first_key=True,
                               key_mangling=('-', '_'),
                               no_tag_node_value_mangle=True,
                               with_recursive_defaults=True,
                               with_pki=True)
 
     return lb
 
 def verify(lb):
     if not lb:
         return None
 
     if 'backend' not in lb or 'service' not in lb:
         raise ConfigError(f'"service" and "backend" must be configured!')
 
     for front, front_config in lb['service'].items():
         if 'port' not in front_config:
             raise ConfigError(f'"{front} service port" must be configured!')
 
         # Check if bind address:port are used by another service
         tmp_address = front_config.get('address', '0.0.0.0')
         tmp_port = front_config['port']
         if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \
                 not is_listen_port_bind_service(int(tmp_port), 'haproxy'):
             raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service')
 
     for back, back_config in lb['backend'].items():
         if 'http_check' in back_config:
             http_check = back_config['http_check']
             if 'expect' in http_check and 'status' in http_check['expect'] and 'string' in http_check['expect']:
                 raise ConfigError(f'"expect status" and "expect string" can not be configured together!')
 
         if 'health_check' in back_config:
             if back_config['mode'] != 'tcp':
                 raise ConfigError(f'backend "{back}" can only be configured with {back_config["health_check"]} ' +
                                   f'health-check whilst in TCP mode!')
             if 'http_check' in back_config:
                 raise ConfigError(f'backend "{back}" cannot be configured with both http-check and health-check!')
 
         if 'server' not in back_config:
             raise ConfigError(f'"{back} server" must be configured!')
 
         for bk_server, bk_server_conf in back_config['server'].items():
             if 'address' not in bk_server_conf or 'port' not in bk_server_conf:
                 raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!')
 
             if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf):
                 raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')
 
         if 'ssl' in back_config:
             if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']):
                 raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!')
 
     # Check if http-response-headers are configured in any frontend/backend where mode != http
     for group in ['service', 'backend']:
         for config_name, config in lb[group].items():
             if 'http_response_headers' in config and config['mode'] != 'http':
                 raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!')
 
     for front, front_config in lb['service'].items():
         for cert in dict_search('ssl.certificate', front_config) or []:
             verify_pki_certificate(lb, cert)
 
     for back, back_config in lb['backend'].items():
         tmp = dict_search('ssl.ca_certificate', back_config)
         if tmp: verify_pki_ca_certificate(lb, tmp)
 
 
 def generate(lb):
     if not lb:
         # Delete /run/haproxy/haproxy.cfg
         config_files = [load_balancing_conf_file, systemd_override]
         for file in config_files:
             if os.path.isfile(file):
                 os.unlink(file)
         # Delete old directories
         if os.path.isdir(load_balancing_dir):
             rmtree(load_balancing_dir, ignore_errors=True)
 
         return None
 
     # Create load-balance dir
     if not os.path.isdir(load_balancing_dir):
         os.mkdir(load_balancing_dir)
 
     loaded_ca_certs = {load_certificate(c['certificate'])
         for c in lb['pki']['ca'].values()} if 'ca' in lb['pki'] else {}
 
     # SSL Certificates for frontend
     for front, front_config in lb['service'].items():
         if 'ssl' not in front_config:
             continue
 
         if 'certificate' in front_config['ssl']:
             cert_names = front_config['ssl']['certificate']
 
             for cert_name in cert_names:
                 pki_cert = lb['pki']['certificate'][cert_name]
                 cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
                 cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
 
                 loaded_pki_cert = load_certificate(pki_cert['certificate'])
                 cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
 
                 write_file(cert_file_path,
                    '\n'.join(encode_certificate(c) for c in cert_full_chain))
 
                 if 'private' in pki_cert and 'key' in pki_cert['private']:
                     loaded_key = load_private_key(pki_cert['private']['key'], passphrase=None, wrap_tags=True)
                     key_pem = encode_private_key(loaded_key, passphrase=None)
                     write_file(cert_key_path, key_pem)
 
     # SSL Certificates for backend
     for back, back_config in lb['backend'].items():
         if 'ssl' not in back_config:
             continue
 
         if 'ca_certificate' in back_config['ssl']:
             ca_name = back_config['ssl']['ca_certificate']
             ca_cert_file_path = os.path.join(load_balancing_dir, f'{ca_name}.pem')
             ca_chains = []
 
             pki_ca_cert = lb['pki']['ca'][ca_name]
             loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
             ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
             ca_chains.append('\n'.join(encode_certificate(c) for c in ca_full_chain))
             write_file(ca_cert_file_path, '\n'.join(ca_chains))
 
     render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb)
     render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb)
 
     return None
 
 def apply(lb):
     call('systemctl daemon-reload')
     if not lb:
         call(f'systemctl stop {systemd_service}')
     else:
         call(f'systemctl reload-or-restart {systemd_service}')
 
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py
index 233d73ba8..45e0129a3 100755
--- a/src/conf_mode/pki.py
+++ b/src/conf_mode/pki.py
@@ -1,517 +1,517 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2021-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from sys import argv
 from sys import exit
 
 from vyos.config import Config
 from vyos.config import config_dict_merge
 from vyos.configdep import set_dependents
 from vyos.configdep import call_dependents
 from vyos.configdict import node_changed
 from vyos.configdiff import Diff
 from vyos.configdiff import get_config_diff
 from vyos.defaults import directories
 from vyos.pki import encode_certificate
 from vyos.pki import is_ca_certificate
 from vyos.pki import load_certificate
 from vyos.pki import load_public_key
 from vyos.pki import load_openssh_public_key
 from vyos.pki import load_openssh_private_key
 from vyos.pki import load_private_key
 from vyos.pki import load_crl
 from vyos.pki import load_dh_parameters
 from vyos.utils.boot import boot_configuration_complete
 from vyos.utils.configfs import add_cli_node
 from vyos.utils.dict import dict_search
 from vyos.utils.dict import dict_search_args
 from vyos.utils.dict import dict_search_recursive
 from vyos.utils.file import read_file
 from vyos.utils.process import call
 from vyos.utils.process import cmd
 from vyos.utils.process import is_systemd_service_active
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 vyos_certbot_dir = directories['certbot']
 
 # keys to recursively search for under specified path
 sync_search = [
     {
         'keys': ['certificate'],
         'path': ['service', 'https'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['interfaces', 'ethernet'],
     },
     {
         'keys': ['certificate', 'ca_certificate', 'dh_params', 'shared_secret_key', 'auth_key', 'crypt_key'],
         'path': ['interfaces', 'openvpn'],
     },
     {
         'keys': ['ca_certificate'],
         'path': ['interfaces', 'sstpc'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
-        'path': ['load_balancing', 'reverse_proxy'],
+        'path': ['load_balancing', 'haproxy'],
     },
     {
         'keys': ['key'],
         'path': ['protocols', 'rpki', 'cache'],
     },
     {
         'keys': ['certificate', 'ca_certificate', 'local_key', 'remote_key'],
         'path': ['vpn', 'ipsec'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['vpn', 'openconnect'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['vpn', 'sstp'],
     },
     {
         'keys': ['certificate', 'ca_certificate'],
         'path': ['service', 'stunnel'],
     }
 ]
 
 # key from other config nodes -> key in pki['changed'] and pki
 sync_translate = {
     'certificate': 'certificate',
     'ca_certificate': 'ca',
     'dh_params': 'dh',
     'local_key': 'key_pair',
     'remote_key': 'key_pair',
     'shared_secret_key': 'openvpn',
     'auth_key': 'openvpn',
     'crypt_key': 'openvpn',
     'key': 'openssh',
 }
 
 def certbot_delete(certificate):
     if not boot_configuration_complete():
         return
     if os.path.exists(f'{vyos_certbot_dir}/renewal/{certificate}.conf'):
         cmd(f'certbot delete --non-interactive --config-dir {vyos_certbot_dir} --cert-name {certificate}')
 
 def certbot_request(name: str, config: dict, dry_run: bool=True):
     # We do not call certbot when booting the system - there is no need to do so and
     # request new certificates during boot/image upgrade as the certbot configuration
     # is stored persistent under /config - thus we do not open the door to transient
     # errors
     if not boot_configuration_complete():
         return
 
     domains = '--domains ' + ' --domains '.join(config['domain_name'])
     tmp = f'certbot certonly --non-interactive --config-dir {vyos_certbot_dir} --cert-name {name} '\
           f'--standalone --agree-tos --no-eff-email --expand --server {config["url"]} '\
           f'--email {config["email"]} --key-type rsa --rsa-key-size {config["rsa_key_size"]} '\
           f'{domains}'
     if 'listen_address' in config:
         tmp += f' --http-01-address {config["listen_address"]}'
     # verify() does not need to actually request a cert but only test for plausability
     if dry_run:
         tmp += ' --dry-run'
 
     cmd(tmp, raising=ConfigError, message=f'ACME certbot request failed for "{name}"!')
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['pki']
 
     pki = conf.get_config_dict(base, key_mangling=('-', '_'),
                                      get_first_key=True,
                                      no_tag_node_value_mangle=True)
 
     if len(argv) > 1 and argv[1] == 'certbot_renew':
         pki['certbot_renew'] = {}
 
     tmp = node_changed(conf, base + ['ca'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
     if tmp:
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'ca' : tmp})
 
     tmp = node_changed(conf, base + ['certificate'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
     if tmp:
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'certificate' : tmp})
 
     tmp = node_changed(conf, base + ['dh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
     if tmp:
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'dh' : tmp})
 
     tmp = node_changed(conf, base + ['key-pair'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
     if tmp:
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'key_pair' : tmp})
 
     tmp = node_changed(conf, base + ['openssh'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
     if tmp:
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'openssh' : tmp})
 
     tmp = node_changed(conf, base + ['openvpn', 'shared-secret'], recursive=True, expand_nodes=Diff.DELETE | Diff.ADD)
     if tmp:
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'openvpn' : tmp})
 
     # We only merge on the defaults of there is a configuration at all
     if conf.exists(base):
         # We have gathered the dict representation of the CLI, but there are default
         # options which we need to update into the dictionary retrived.
         default_values = conf.get_config_defaults(**pki.kwargs, recursive=True)
         # remove ACME default configuration if unused by CLI
         if 'certificate' in pki:
             for name, cert_config in pki['certificate'].items():
                 if 'acme' not in cert_config:
                     # Remove ACME default values
                     del default_values['certificate'][name]['acme']
 
         # merge CLI and default dictionary
         pki = config_dict_merge(default_values, pki)
 
     # Certbot triggered an external renew of the certificates.
     # Mark all ACME based certificates as "changed" to trigger
     # update of dependent services
     if 'certificate' in pki and 'certbot_renew' in pki:
         renew = []
         for name, cert_config in pki['certificate'].items():
             if 'acme' in cert_config:
                 renew.append(name)
         # If triggered externally by certbot, certificate key is not present in changed
         if 'changed' not in pki: pki.update({'changed':{}})
         pki['changed'].update({'certificate' : renew})
 
     # We need to get the entire system configuration to verify that we are not
     # deleting a certificate that is still referenced somewhere!
     pki['system'] = conf.get_config_dict([], key_mangling=('-', '_'),
                                          get_first_key=True,
                                          no_tag_node_value_mangle=True)
     D = get_config_diff(conf)
 
     for search in sync_search:
         for key in search['keys']:
             changed_key = sync_translate[key]
             if 'changed' not in pki or changed_key not in pki['changed']:
                 continue
 
             for item_name in pki['changed'][changed_key]:
                 node_present = False
                 if changed_key == 'openvpn':
                     node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
                 else:
                     node_present = dict_search_args(pki, changed_key, item_name)
 
                 if node_present:
                     search_dict = dict_search_args(pki['system'], *search['path'])
                     if not search_dict:
                         continue
                     for found_name, found_path in dict_search_recursive(search_dict, key):
                         if isinstance(found_name, list) and item_name not in found_name:
                             continue
 
                         if isinstance(found_name, str) and found_name != item_name:
                             continue
 
                         path = search['path']
                         path_str = ' '.join(path + found_path)
                         #print(f'PKI: Updating config: {path_str} {item_name}')
 
                         if path[0] == 'interfaces':
                             ifname = found_path[0]
                             if not D.node_changed_presence(path + [ifname]):
                                 set_dependents(path[1], conf, ifname)
                         else:
                             if not D.node_changed_presence(path):
                                 set_dependents(path[1], conf)
 
     return pki
 
 def is_valid_certificate(raw_data):
     # If it loads correctly we're good, or return False
     return load_certificate(raw_data, wrap_tags=True)
 
 def is_valid_ca_certificate(raw_data):
     # Check if this is a valid certificate with CA attributes
     cert = load_certificate(raw_data, wrap_tags=True)
     if not cert:
         return False
     return is_ca_certificate(cert)
 
 def is_valid_public_key(raw_data):
     # If it loads correctly we're good, or return False
     return load_public_key(raw_data, wrap_tags=True)
 
 def is_valid_private_key(raw_data, protected=False):
     # If it loads correctly we're good, or return False
     # With encrypted private keys, we always return true as we cannot ask for password to verify
     if protected:
         return True
     return load_private_key(raw_data, passphrase=None, wrap_tags=True)
 
 def is_valid_openssh_public_key(raw_data, type):
     # If it loads correctly we're good, or return False
     return load_openssh_public_key(raw_data, type)
 
 def is_valid_openssh_private_key(raw_data, protected=False):
     # If it loads correctly we're good, or return False
     # With encrypted private keys, we always return true as we cannot ask for password to verify
     if protected:
         return True
     return load_openssh_private_key(raw_data, passphrase=None, wrap_tags=True)
 
 def is_valid_crl(raw_data):
     # If it loads correctly we're good, or return False
     return load_crl(raw_data, wrap_tags=True)
 
 def is_valid_dh_parameters(raw_data):
     # If it loads correctly we're good, or return False
     return load_dh_parameters(raw_data, wrap_tags=True)
 
 def verify(pki):
     if not pki:
         return None
 
     if 'ca' in pki:
         for name, ca_conf in pki['ca'].items():
             if 'certificate' in ca_conf:
                 if not is_valid_ca_certificate(ca_conf['certificate']):
                     raise ConfigError(f'Invalid certificate on CA certificate "{name}"')
 
             if 'private' in ca_conf and 'key' in ca_conf['private']:
                 private = ca_conf['private']
                 protected = 'password_protected' in private
 
                 if not is_valid_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid private key on CA certificate "{name}"')
 
             if 'crl' in ca_conf:
                 ca_crls = ca_conf['crl']
                 if isinstance(ca_crls, str):
                     ca_crls = [ca_crls]
 
                 for crl in ca_crls:
                     if not is_valid_crl(crl):
                         raise ConfigError(f'Invalid CRL on CA certificate "{name}"')
 
     if 'certificate' in pki:
         for name, cert_conf in pki['certificate'].items():
             if 'certificate' in cert_conf:
                 if not is_valid_certificate(cert_conf['certificate']):
                     raise ConfigError(f'Invalid certificate on certificate "{name}"')
 
             if 'private' in cert_conf and 'key' in cert_conf['private']:
                 private = cert_conf['private']
                 protected = 'password_protected' in private
 
                 if not is_valid_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid private key on certificate "{name}"')
 
             if 'acme' in cert_conf:
                 if 'domain_name' not in cert_conf['acme']:
                     raise ConfigError(f'At least one domain-name is required to request '\
                                     f'certificate for "{name}" via ACME!')
 
                 if 'email' not in cert_conf['acme']:
                     raise ConfigError(f'An email address is required to request '\
                                     f'certificate for "{name}" via ACME!')
 
                 if 'certbot_renew' not in pki:
                     # Only run the ACME command if something on this entity changed,
                     # as this is time intensive
                     tmp = dict_search('changed.certificate', pki)
                     if tmp != None and name in tmp:
                         certbot_request(name, cert_conf['acme'])
 
     if 'dh' in pki:
         for name, dh_conf in pki['dh'].items():
             if 'parameters' in dh_conf:
                 if not is_valid_dh_parameters(dh_conf['parameters']):
                     raise ConfigError(f'Invalid DH parameters on "{name}"')
 
     if 'key_pair' in pki:
         for name, key_conf in pki['key_pair'].items():
             if 'public' in key_conf and 'key' in key_conf['public']:
                 if not is_valid_public_key(key_conf['public']['key']):
                     raise ConfigError(f'Invalid public key on key-pair "{name}"')
 
             if 'private' in key_conf and 'key' in key_conf['private']:
                 private = key_conf['private']
                 protected = 'password_protected' in private
                 if not is_valid_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid private key on key-pair "{name}"')
 
     if 'openssh' in pki:
         for name, key_conf in pki['openssh'].items():
             if 'public' in key_conf and 'key' in key_conf['public']:
                 if 'type' not in key_conf['public']:
                     raise ConfigError(f'Must define OpenSSH public key type for "{name}"')
                 if not is_valid_openssh_public_key(key_conf['public']['key'], key_conf['public']['type']):
                     raise ConfigError(f'Invalid OpenSSH public key "{name}"')
 
             if 'private' in key_conf and 'key' in key_conf['private']:
                 private = key_conf['private']
                 protected = 'password_protected' in private
                 if not is_valid_openssh_private_key(private['key'], protected):
                     raise ConfigError(f'Invalid OpenSSH private key "{name}"')
 
     if 'x509' in pki:
         if 'default' in pki['x509']:
             default_values = pki['x509']['default']
             if 'country' in default_values:
                 country = default_values['country']
                 if len(country) != 2 or not country.isalpha():
                     raise ConfigError(f'Invalid default country value. Value must be 2 alpha characters.')
 
     if 'changed' in pki:
         # if the list is getting longer, we can move to a dict() and also embed the
         # search key as value from line 173 or 176
         for search in sync_search:
             for key in search['keys']:
                 changed_key = sync_translate[key]
 
                 if changed_key not in pki['changed']:
                     continue
 
                 for item_name in pki['changed'][changed_key]:
                     node_present = False
                     if changed_key == 'openvpn':
                         node_present = dict_search_args(pki, 'openvpn', 'shared_secret', item_name)
                     else:
                         node_present = dict_search_args(pki, changed_key, item_name)
 
                     if not node_present:
                         search_dict = dict_search_args(pki['system'], *search['path'])
 
                         if not search_dict:
                             continue
 
                         for found_name, found_path in dict_search_recursive(search_dict, key):
                             if found_name == item_name:
                                 path_str = " ".join(search['path'] + found_path)
                                 raise ConfigError(f'PKI object "{item_name}" still in use by "{path_str}"')
 
     return None
 
 def generate(pki):
     if not pki:
         return None
 
     # Certbot renewal only needs to re-trigger the services to load up the
     # new PEM file
     if 'certbot_renew' in pki:
         return None
 
     certbot_list = []
     certbot_list_on_disk = []
     if os.path.exists(f'{vyos_certbot_dir}/live'):
         certbot_list_on_disk = [f.path.split('/')[-1] for f in os.scandir(f'{vyos_certbot_dir}/live') if f.is_dir()]
 
     if 'certificate' in pki:
         changed_certificates = dict_search('changed.certificate', pki)
         for name, cert_conf in pki['certificate'].items():
             if 'acme' in cert_conf:
                 certbot_list.append(name)
                 # generate certificate if not found on disk
                 if name not in certbot_list_on_disk:
                     certbot_request(name, cert_conf['acme'], dry_run=False)
                 elif changed_certificates != None and name in changed_certificates:
                     # when something for the certificate changed, we should delete it
                     if name in certbot_list_on_disk:
                         certbot_delete(name)
                     certbot_request(name, cert_conf['acme'], dry_run=False)
 
     # Cleanup certbot configuration and certificates if no longer in use by CLI
     # Get foldernames under vyos_certbot_dir which each represent a certbot cert
     if os.path.exists(f'{vyos_certbot_dir}/live'):
         for cert in certbot_list_on_disk:
             # ACME certificate is no longer in use by CLI remove it
             if cert not in certbot_list:
                 certbot_delete(cert)
                 continue
             # ACME not enabled for individual certificate - bail out early
             if 'acme' not in pki['certificate'][cert]:
                 continue
 
             # Read in ACME certificate chain information
             tmp = read_file(f'{vyos_certbot_dir}/live/{cert}/chain.pem')
             tmp = load_certificate(tmp, wrap_tags=False)
             cert_chain_base64 = "".join(encode_certificate(tmp).strip().split("\n")[1:-1])
 
             # Check if CA chain certificate is already present on CLI to avoid adding
             # a duplicate. This only checks for manual added CA certificates and not
             # auto added ones with the AUTOCHAIN_ prefix
             autochain_prefix = 'AUTOCHAIN_'
             ca_cert_present = False
             if 'ca' in pki:
                 for ca_base64, cli_path in dict_search_recursive(pki['ca'], 'certificate'):
                     # Ignore automatic added CA certificates
                     if any(item.startswith(autochain_prefix) for item in cli_path):
                         continue
                     if cert_chain_base64 == ca_base64:
                         ca_cert_present = True
 
             if not ca_cert_present:
                 tmp = dict_search_args(pki, 'ca', f'{autochain_prefix}{cert}', 'certificate')
                 if not bool(tmp) or tmp != cert_chain_base64:
                     print(f'Adding/replacing automatically imported CA certificate for "{cert}" ...')
                     add_cli_node(['pki', 'ca', f'{autochain_prefix}{cert}', 'certificate'], value=cert_chain_base64)
 
     return None
 
 def apply(pki):
     systemd_certbot_name = 'certbot.timer'
     if not pki:
         call(f'systemctl stop {systemd_certbot_name}')
         return None
 
     has_certbot = False
     if 'certificate' in pki:
         for name, cert_conf in pki['certificate'].items():
             if 'acme' in cert_conf:
                 has_certbot = True
                 break
 
     if not has_certbot:
         call(f'systemctl stop {systemd_certbot_name}')
     elif has_certbot and not is_systemd_service_active(systemd_certbot_name):
         call(f'systemctl restart {systemd_certbot_name}')
 
     if 'changed' in pki:
         call_dependents()
 
     return None
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/migration-scripts/reverse-proxy/1-to-2 b/src/migration-scripts/reverse-proxy/1-to-2
new file mode 100755
index 000000000..61612bc36
--- /dev/null
+++ b/src/migration-scripts/reverse-proxy/1-to-2
@@ -0,0 +1,27 @@
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library.  If not, see <http://www.gnu.org/licenses/>.
+
+# T6745: Rename base node to haproxy
+
+from vyos.configtree import ConfigTree
+
+base = ['load-balancing', 'reverse-proxy']
+
+def migrate(config: ConfigTree) -> None:
+    if not config.exists(base):
+        # Nothing to do
+        return
+
+    config.rename(base, 'haproxy')
diff --git a/src/op_mode/reverseproxy.py b/src/op_mode/load-balancing_haproxy.py
similarity index 97%
rename from src/op_mode/reverseproxy.py
rename to src/op_mode/load-balancing_haproxy.py
index 19704182a..ae6734e16 100755
--- a/src/op_mode/reverseproxy.py
+++ b/src/op_mode/load-balancing_haproxy.py
@@ -1,237 +1,237 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2023-2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import json
 import socket
 import sys
 
 from tabulate import tabulate
 from vyos.configquery import ConfigTreeQuery
 
 import vyos.opmode
 
 socket_path = '/run/haproxy/admin.sock'
 timeout = 5
 
 
 def _execute_haproxy_command(command):
     """Execute a command on the HAProxy UNIX socket and retrieve the response.
 
     Args:
         command (str): The command to be executed.
 
     Returns:
         str: The response received from the HAProxy UNIX socket.
 
     Raises:
         socket.error: If there is an error while connecting or communicating with the socket.
 
     Finally:
         Closes the socket connection.
 
     Notes:
         - HAProxy expects a newline character at the end of the command.
         - The socket connection is established using the HAProxy UNIX socket.
         - The response from the socket is received and decoded.
 
     Example:
         response = _execute_haproxy_command('show stat')
         print(response)
     """
     try:
         # HAProxy expects new line for command
         command = f'{command}\n'
 
         # Connect to the HAProxy UNIX socket
         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         sock.connect(socket_path)
 
         # Set the socket timeout
         sock.settimeout(timeout)
 
         # Send the command
         sock.sendall(command.encode())
 
         # Receive and decode the response
         response = b''
         while True:
             data = sock.recv(4096)
             if not data:
                 break
             response += data
         response = response.decode()
 
         return (response)
 
     except socket.error as e:
         print(f"Error: {e}")
 
     finally:
         # Close the socket
         sock.close()
 
 
 def _convert_seconds(seconds):
     """Convert seconds to days, hours, minutes, and seconds.
 
     Args:
         seconds (int): The number of seconds to convert.
 
     Returns:
         tuple: A tuple containing the number of days, hours, minutes, and seconds.
     """
     minutes = seconds // 60
     hours = minutes // 60
     days = hours // 24
 
     return days, hours % 24, minutes % 60, seconds % 60
 
 
 def _last_change_format(seconds):
     """Format the time components into a string representation.
 
     Args:
         seconds (int): The total number of seconds.
 
     Returns:
         str: The formatted time string with days, hours, minutes, and seconds.
 
     Examples:
         >>> _last_change_format(1434)
         '23m54s'
         >>> _last_change_format(93734)
         '1d0h23m54s'
         >>> _last_change_format(85434)
         '23h23m54s'
     """
     days, hours, minutes, seconds = _convert_seconds(seconds)
     time_format = ""
 
     if days:
         time_format += f"{days}d"
     if hours:
         time_format += f"{hours}h"
     if minutes:
         time_format += f"{minutes}m"
     if seconds:
         time_format += f"{seconds}s"
 
     return time_format
 
 
 def _get_json_data():
     """Get haproxy data format JSON"""
     return _execute_haproxy_command('show stat json')
 
 
 def _get_raw_data():
     """Retrieve raw data from JSON and organize it into a dictionary.
 
     Returns:
         dict: A dictionary containing the organized data categorized
               into frontend, backend, and server.
     """
 
     data = json.loads(_get_json_data())
     lb_dict = {'frontend': [], 'backend': [], 'server': []}
 
     for key in data:
         frontend = []
         backend = []
         server = []
         for entry in key:
             obj_type = entry['objType'].lower()
             position = entry['field']['pos']
             name = entry['field']['name']
             value = entry['value']['value']
 
             dict_entry = {'pos': position, 'name': {name: value}}
 
             if obj_type == 'frontend':
                 frontend.append(dict_entry)
             elif obj_type == 'backend':
                 backend.append(dict_entry)
             elif obj_type == 'server':
                 server.append(dict_entry)
 
         if len(frontend) > 0:
             lb_dict['frontend'].append(frontend)
         if len(backend) > 0:
             lb_dict['backend'].append(backend)
         if len(server) > 0:
             lb_dict['server'].append(server)
 
     return lb_dict
 
 
 def _get_formatted_output(data):
     """
     Format the data into a tabulated output.
 
     Args:
         data (dict): The data to be formatted.
 
     Returns:
         str: The tabulated output representing the formatted data.
     """
     table = []
     headers = [
         "Proxy name", "Role", "Status", "Req rate", "Resp time", "Last change"
     ]
 
     for key in data:
         for item in data[key]:
             row = [None] * len(headers)
 
             for element in item:
                 if 'pxname' in element['name']:
                     row[0] = element['name']['pxname']
                 elif 'svname' in element['name']:
                     row[1] = element['name']['svname']
                 elif 'status' in element['name']:
                     row[2] = element['name']['status']
                 elif 'req_rate' in element['name']:
                     row[3] = element['name']['req_rate']
                 elif 'rtime' in element['name']:
                     row[4] = f"{element['name']['rtime']} ms"
                 elif 'lastchg' in element['name']:
                     row[5] = _last_change_format(element['name']['lastchg'])
             table.append(row)
 
     out = tabulate(table, headers, numalign="left")
     return out
 
 
 def show(raw: bool):
     config = ConfigTreeQuery()
-    if not config.exists('load-balancing reverse-proxy'):
-        raise vyos.opmode.UnconfiguredSubsystem('Reverse-proxy is not configured')
+    if not config.exists('load-balancing haproxy'):
+        raise vyos.opmode.UnconfiguredSubsystem('Haproxy is not configured')
 
     data = _get_raw_data()
     if raw:
         return data
     else:
         return _get_formatted_output(data)
 
 
 if __name__ == '__main__':
     try:
         res = vyos.opmode.run(sys.modules[__name__])
         if res:
             print(res)
     except (ValueError, vyos.opmode.Error) as e:
         print(e)
         sys.exit(1)
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index a83c8b9d8..3b0031f34 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -1,149 +1,149 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2024 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 as
 # published by the Free Software Foundation.
 #
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import sys
 import typing
 import vyos.opmode
 
 from vyos.configquery import ConfigTreeQuery
 from vyos.utils.process import call
 from vyos.utils.commit import commit_in_progress
 
 config = ConfigTreeQuery()
 
 service_map = {
     'dhcp': {
         'systemd_service': 'kea-dhcp4-server',
         'path': ['service', 'dhcp-server'],
     },
     'dhcpv6': {
         'systemd_service': 'kea-dhcp6-server',
         'path': ['service', 'dhcpv6-server'],
     },
     'dns_dynamic': {
         'systemd_service': 'ddclient',
         'path': ['service', 'dns', 'dynamic'],
     },
     'dns_forwarding': {
         'systemd_service': 'pdns-recursor',
         'path': ['service', 'dns', 'forwarding'],
     },
+    'haproxy': {
+        'systemd_service': 'haproxy',
+        'path': ['load-balancing', 'haproxy'],
+    },
     'igmp_proxy': {
         'systemd_service': 'igmpproxy',
         'path': ['protocols', 'igmp-proxy'],
     },
     'ipsec': {
         'systemd_service': 'strongswan',
         'path': ['vpn', 'ipsec'],
     },
     'mdns_repeater': {
         'systemd_service': 'avahi-daemon',
         'path': ['service', 'mdns', 'repeater'],
     },
-    'reverse_proxy': {
-        'systemd_service': 'haproxy',
-        'path': ['load-balancing', 'reverse-proxy'],
-    },
     'router_advert': {
         'systemd_service': 'radvd',
         'path': ['service', 'router-advert'],
     },
     'snmp': {
         'systemd_service': 'snmpd',
     },
     'ssh': {
         'systemd_service': 'ssh',
     },
     'suricata': {
         'systemd_service': 'suricata',
     },
     'vrrp': {
         'systemd_service': 'keepalived',
         'path': ['high-availability', 'vrrp'],
     },
     'webproxy': {
         'systemd_service': 'squid',
     },
 }
 services = typing.Literal[
     'dhcp',
     'dhcpv6',
     'dns_dynamic',
     'dns_forwarding',
+    'haproxy',
     'igmp_proxy',
     'ipsec',
     'mdns_repeater',
-    'reverse_proxy',
     'router_advert',
     'snmp',
     'ssh',
     'suricata',
     'vrrp',
     'webproxy',
 ]
 
 
 def _verify(func):
     """Decorator checks if DHCP(v6) config exists"""
     from functools import wraps
 
     @wraps(func)
     def _wrapper(*args, **kwargs):
         config = ConfigTreeQuery()
         name = kwargs.get('name')
         human_name = name.replace('_', '-')
 
         if commit_in_progress():
             print(f'Cannot restart {human_name} service while a commit is in progress')
             sys.exit(1)
 
         # Get optional CLI path from service_mapping dict
         # otherwise use "service name" CLI path
         path = ['service', name]
         if 'path' in service_map[name]:
             path = service_map[name]['path']
 
         # Check if config does not exist
         if not config.exists(path):
             raise vyos.opmode.UnconfiguredSubsystem(
                 f'Service {human_name} is not configured!'
             )
         if config.exists(path + ['disable']):
             raise vyos.opmode.UnconfiguredSubsystem(
                 f'Service {human_name} is disabled!'
             )
         return func(*args, **kwargs)
 
     return _wrapper
 
 
 @_verify
 def restart_service(raw: bool, name: services, vrf: typing.Optional[str]):
     systemd_service = service_map[name]['systemd_service']
     if vrf:
         call(f'systemctl restart "{systemd_service}@{vrf}.service"')
     else:
         call(f'systemctl restart "{systemd_service}.service"')
 
 
 if __name__ == '__main__':
     try:
         res = vyos.opmode.run(sys.modules[__name__])
         if res:
             print(res)
     except (ValueError, vyos.opmode.Error) as e:
         print(e)
         sys.exit(1)