diff --git a/ansible/roles/ironic/defaults/main.yml b/ansible/roles/ironic/defaults/main.yml
index a2ba1ce644..900f7f5b76 100644
--- a/ansible/roles/ironic/defaults/main.yml
+++ b/ansible/roles/ironic/defaults/main.yml
@@ -66,7 +66,9 @@ ironic_services:
   ironic-ipxe:
     container_name: ironic_ipxe
     group: ironic-ipxe
-    enabled: "{{ enable_ironic_ipxe | bool }}"
+    # NOTE(mgoddard): This container is always enabled, since may be used by
+    # the direct deploy driver.
+    enabled: true
     image: "{{ ironic_pxe_image_full }}"
     volumes: "{{ ironic_ipxe_default_volumes + ironic_ipxe_extra_volumes }}"
     dimensions: "{{ ironic_ipxe_dimensions }}"
diff --git a/ansible/roles/ironic/templates/ironic.conf.j2 b/ansible/roles/ironic/templates/ironic.conf.j2
index e0c5064483..f385fdc812 100644
--- a/ansible/roles/ironic/templates/ironic.conf.j2
+++ b/ansible/roles/ironic/templates/ironic.conf.j2
@@ -184,15 +184,18 @@ deploy_logs_collect = always
 [pxe]
 pxe_append_params = nofb nomodeset vga=normal console=tty0 console=ttyS0,{{ ironic_console_serial_speed }}
 {% if enable_ironic_ipxe | bool %}
+{# NOTE(mgoddard): iPXE uses the TFTP image cache (tftp_master_path, default
+   /tftpboot/master_images), in which images get hard linked to the http_root
+   directory (/httpboot). These must be on the same device, but /httpboot and
+   /tftpboot live in separate Docker volumes. Override the default paths for
+   iPXE to place them both in /httpboot. This prevents mixing PXE and iPXE. #}
 tftp_root = /httpboot
 tftp_master_path = /httpboot/master_images
 tftp_server = {{ api_interface_address }}
 {% endif %}
 
-{% if enable_ironic_ipxe | bool %}
 [deploy]
 http_url = {{ ironic_ipxe_url }}
-{% endif %}
 
 [oslo_middleware]
 enable_proxy_headers_parsing = True
diff --git a/doc/source/reference/bare-metal/ironic-guide.rst b/doc/source/reference/bare-metal/ironic-guide.rst
index b261f9cc75..8add19ed96 100644
--- a/doc/source/reference/bare-metal/ironic-guide.rst
+++ b/doc/source/reference/bare-metal/ironic-guide.rst
@@ -73,10 +73,11 @@ true in ``/etc/kolla/globals.yml``:
 
 .. code-block:: yaml
 
-    enable_ironic_ipxe: "yes"
+   enable_ironic_ipxe: "yes"
 
-This will enable deployment of a docker container, called ironic_ipxe, running
-the web server which iPXE uses to obtain it's boot images.
+When iPXE booting is enabled, the ``ironic_ipxe`` container is used to serve
+the iPXE boot images as described below. Regardless of the setting above, the
+same container is used to support the ``direct`` deploy interface.
 
 The port used for the iPXE webserver is controlled via ``ironic_ipxe_port`` in
 ``/etc/kolla/globals.yml``:
@@ -94,6 +95,17 @@ The following changes will occur if iPXE booting is enabled:
   environment. You may also boot directly to iPXE by some other means e.g by
   burning it to the option rom of your ethernet card.
 
+Note that due to a limitation in Kolla Ansible, PXE and iPXE cannot be used
+together in a single deployment.
+
+In order to enable the iPXE driver in Ironic, set the ``[DEFAULT]
+enabled_boot_interfaces`` option in ``/etc/kolla/config/ironic.conf``:
+
+.. code-block:: yaml
+
+   [DEFAULT]
+   enabled_boot_interfaces = ipxe
+
 Deployment
 ~~~~~~~~~~
 Run the deploy as usual:
diff --git a/releasenotes/notes/enable-ipxe-cf461344bdb99881.yaml b/releasenotes/notes/enable-ipxe-cf461344bdb99881.yaml
new file mode 100644
index 0000000000..bbf10823fa
--- /dev/null
+++ b/releasenotes/notes/enable-ipxe-cf461344bdb99881.yaml
@@ -0,0 +1,10 @@
+---
+upgrade:
+  - |
+    An HTTP server is now always deployed for Ironic conductor, while
+    previously it was only deployed when iPXE is enabled.
+
+    In the Xena release, Ironic removed the iSCSI driver. The recommended
+    deploy driver is ``direct``, which uses HTTP to transfer the disk image.
+    This requires an HTTP server, and the simplest option is to use the one
+    previously deployed when ``enable_ironic_ipxe`` is set to ``true``.