diff --git a/ansible/roles/ironic/defaults/main.yml b/ansible/roles/ironic/defaults/main.yml
index 2305157d6883eb42c7df4d5cf4fad1e552329b04..b4c0d2c1f79b5447fa98b8f181e9432d4fa0649e 100644
--- a/ansible/roles/ironic/defaults/main.yml
+++ b/ansible/roles/ironic/defaults/main.yml
@@ -16,12 +16,14 @@ ironic_services:
         external: false
         port: "{{ ironic_api_port }}"
         listen_port: "{{ ironic_api_listen_port }}"
+        tls_backend: "{{ ironic_enable_tls_backend }}"
       ironic_api_external:
         enabled: "{{ enable_ironic }}"
         mode: "http"
         external: true
         port: "{{ ironic_api_port }}"
         listen_port: "{{ ironic_api_listen_port }}"
+        tls_backend: "{{ ironic_enable_tls_backend }}"
   ironic-conductor:
     container_name: ironic_conductor
     group: ironic-conductor
@@ -253,3 +255,8 @@ ironic_ks_users:
     user: "{{ ironic_inspector_keystone_user }}"
     password: "{{ ironic_inspector_keystone_password }}"
     role: "admin"
+
+####################
+# TLS
+####################
+ironic_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
diff --git a/ansible/roles/ironic/handlers/main.yml b/ansible/roles/ironic/handlers/main.yml
index 458d648e1bc31bfd57b28259455e11e3b4713721..a1b701506ec08176757e758c602639affb6d32e8 100644
--- a/ansible/roles/ironic/handlers/main.yml
+++ b/ansible/roles/ironic/handlers/main.yml
@@ -40,6 +40,7 @@
     module_name: uri
     module_args:
       url: "{{ ironic_internal_endpoint }}"
+      validate_certs: false
   register: result
   until: result is success
   retries: 12
diff --git a/ansible/roles/ironic/tasks/config.yml b/ansible/roles/ironic/tasks/config.yml
index 26111f48f5fe519849554440535b97ccf86d692c..45410533b5697557abcc28d1494e785376f364f1 100644
--- a/ansible/roles/ironic/tasks/config.yml
+++ b/ansible/roles/ironic/tasks/config.yml
@@ -33,7 +33,7 @@
 
 - include_tasks: copy-certs.yml
   when:
-    - kolla_copy_ca_into_containers | bool
+    - kolla_copy_ca_into_containers | bool or ironic_enable_tls_backend | bool
 
 - name: Copying over config.json files for services
   template:
@@ -244,5 +244,17 @@
   notify:
     - "Restart {{ item.key }} container"
 
+- name: Copying over ironic-api-wsgi.conf
+  template:
+    src: "ironic-api-wsgi.conf.j2"
+    dest: "{{ node_config_directory }}/ironic-api/ironic-api-wsgi.conf"
+    mode: "0660"
+  become: true
+  when:
+    - inventory_hostname in groups["ironic-api"]
+    - ironic_services["ironic-api"].enabled | bool
+  notify:
+    - "Restart ironic-api container"
+
 - import_tasks: check-containers.yml
   when: kolla_action != "config"
diff --git a/ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2 b/ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2
new file mode 100644
index 0000000000000000000000000000000000000000..c83138f274e4dd0f2018bd8485cdc472a057cf7f
--- /dev/null
+++ b/ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2
@@ -0,0 +1,50 @@
+{% set ironic_log_dir = '/var/log/kolla/ironic' %}
+{% set wsgi_directory = '/usr/bin' if ironic_install_type == 'binary' else '/var/lib/kolla/venv/bin' %}
+{% if ironic_enable_tls_backend | bool %}
+{% if kolla_base_distro in ['centos']  %}
+LoadModule ssl_module /usr/lib64/httpd/modules/mod_ssl.so
+{% else %}
+LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
+{% endif %}
+{% endif %}
+Listen {{ api_interface_address | put_address_in_context('url') }}:{{ ironic_api_listen_port }}
+
+ServerSignature Off
+ServerTokens Prod
+TraceEnable off
+KeepAliveTimeout {{ kolla_httpd_keep_alive }}
+
+<Directory "{{ wsgi_directory }}">
+    <FilesMatch "^ironic-api-wsgi$">
+        Options None
+        Require all granted
+    </FilesMatch>
+</Directory>
+
+ErrorLog "{{ ironic_log_dir }}/apache-error.log"
+<IfModule log_config_module>
+CustomLog "{{ ironic_log_dir }}/apache-access.log" common
+</IfModule>
+
+{% if ironic_logging_debug | bool %}
+LogLevel info
+{% endif %}
+
+<VirtualHost *:{{ ironic_api_listen_port }}>
+    WSGIDaemonProcess ironic-api processes={{ openstack_service_workers }} threads=1 user=ironic group=ironic display-name=%{GROUP}
+    WSGIProcessGroup ironic-api
+    WSGIScriptAlias / {{ wsgi_directory }}/ironic-api-wsgi
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+    <IfVersion >= 2.4>
+      ErrorLogFormat "%{cu}t %M"
+    </IfVersion>
+    ErrorLog "{{ ironic_log_dir }}/ironic-api-error.log"
+    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
+    CustomLog "{{ ironic_log_dir }}/ironic-api-access.log" logformat
+{% if ironic_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/ironic/certs/ironic-cert.pem
+    SSLCertificateKeyFile /etc/ironic/certs/ironic-key.pem
+{% endif %}
+</VirtualHost>
diff --git a/ansible/roles/ironic/templates/ironic-api.json.j2 b/ansible/roles/ironic/templates/ironic-api.json.j2
index 075b0d04eef355eb7f1db2a28ec2466d835fb7a3..a03a6a6719f2f63223ca74ddac74f2ff79dfba00 100644
--- a/ansible/roles/ironic/templates/ironic-api.json.j2
+++ b/ansible/roles/ironic/templates/ironic-api.json.j2
@@ -1,17 +1,37 @@
+{% set apache_binary = 'apache2' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd' %}
+{% set apache_conf_dir = 'apache2/conf-enabled' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd/conf.d' %}
 {
-    "command": "ironic-api",
+    "command": "/usr/sbin/{{ apache_binary }} -DFOREGROUND",
     "config_files": [
         {
             "source": "{{ container_config_directory }}/ironic.conf",
             "dest": "/etc/ironic/ironic.conf",
             "owner": "ironic",
             "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ironic-api-wsgi.conf",
+            "dest": "/etc/{{ apache_conf_dir }}/ironic-api-wsgi.conf",
+            "owner": "ironic",
+            "perm": "0600"
         }{% if ironic_policy_file is defined %},
         {
             "source": "{{ container_config_directory }}/{{ ironic_policy_file }}",
             "dest": "/etc/ironic/{{ ironic_policy_file }}",
             "owner": "ironic",
             "perm": "0600"
+        }{% endif %}{% if ironic_enable_tls_backend | bool %},
+        {
+            "source": "{{ container_config_directory }}/ironic-cert.pem",
+            "dest": "/etc/ironic/certs/ironic-cert.pem",
+            "owner": "ironic",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ironic-key.pem",
+            "dest": "/etc/ironic/certs/ironic-key.pem",
+            "owner": "ironic",
+            "perm": "0600"
         }{% endif %}
     ],
     "permissions": [
diff --git a/ansible/roles/ironic/templates/ironic.conf.j2 b/ansible/roles/ironic/templates/ironic.conf.j2
index 7e79cd278974a8d9c1dfaeec8db428570044be61..666adc59343201ddd2805e1b65ab67a39f505da5 100644
--- a/ansible/roles/ironic/templates/ironic.conf.j2
+++ b/ansible/roles/ironic/templates/ironic.conf.j2
@@ -35,13 +35,6 @@ driver = noop
 policy_file = {{ ironic_policy_file }}
 {% endif %}
 
-{% if service_name == 'ironic-api' %}
-[api]
-host_ip = {{ api_interface_address }}
-port = {{ ironic_api_listen_port }}
-api_workers = {{ openstack_service_workers }}
-{% endif %}
-
 {% if service_name == 'ironic-conductor' %}
 [conductor]
 automated_clean=false
diff --git a/releasenotes/notes/encrypt-additional-services-backend-haproxy-29467a9771e99917.yaml b/releasenotes/notes/encrypt-additional-services-backend-haproxy-29467a9771e99917.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..76e8326172d1230b02e04f585135e5ed84622753
--- /dev/null
+++ b/releasenotes/notes/encrypt-additional-services-backend-haproxy-29467a9771e99917.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Adds configuration options to enable backend TLS encryption from HAProxy
+    to the Nova and Ironic services. When used in conjunction with enabling TLS
+    for service API endpoints, network communcation will be encrypted end to
+    end, from client through HAProxy to the backend service.
diff --git a/tests/run.yml b/tests/run.yml
index dc4eb1665c5c1123f616be2b27f54d4cf5c923da..c8df10e73ab8ade2a53cdddfbcbe90647bd65742 100644
--- a/tests/run.yml
+++ b/tests/run.yml
@@ -421,6 +421,8 @@
             cmd: test-ironic.sh
             executable: /bin/bash
             chdir: "{{ kolla_ansible_src_dir }}"
+          environment:
+            TLS_ENABLED: "{{ tls_enabled }}"
           when: scenario == "ironic"
 
         - name: Run test-magnum.sh script
diff --git a/tests/test-ironic.sh b/tests/test-ironic.sh
index 6cc3ede3f37203b66b59f9839f7f1c97d195a3ac..9e7876f2a1b052412d580780a6a608e9d19a5bac 100755
--- a/tests/test-ironic.sh
+++ b/tests/test-ironic.sh
@@ -35,7 +35,13 @@ function wait_for_placement_resources {
     for i in $(seq 1 120); do
         # Fetch provider UUIDs from Placement
         local providers
-        providers=$(curl -sH "X-Auth-Token: $token" $endpoint/resource_providers \
+        args=(
+            -sH "X-Auth-Token: $token"
+        )
+        if [[ "$TLS_ENABLED" = "True" ]]; then
+            args+=(--cacert $OS_CACERT)
+        fi
+        providers=$(curl "${args[@]}" $endpoint/resource_providers \
             | ./jq -r '.resource_providers[].uuid')
 
         local p
@@ -46,7 +52,7 @@ function wait_for_placement_resources {
             # A resource class inventory record looks something like
             # {"max_unit": 1, "min_unit": 1, "step_size": 1, "reserved": 0, "total": 1, "allocation_ratio": 1}
             # Subtract reserved from total (defaulting both to 0)
-            amount=$(curl -sH "X-Auth-Token: $token" $endpoint/resource_providers/$p/inventories \
+            amount=$(curl "${args[@]}" $endpoint/resource_providers/$p/inventories \
                 | ./jq ".inventories.CUSTOM_$resource_class as \$cls
                     | (\$cls.total // 0) - (\$cls.reserved // 0)")
             if [ $amount -gt 0 ]; then