diff --git a/ansible/group_vars/all/compute b/ansible/group_vars/all/compute
index a31e6db65542348e1daf6a0d46f6cda8c1dd8566..6e186963c3be97b9063d1827a6a8a9b4e33841dd 100644
--- a/ansible/group_vars/all/compute
+++ b/ansible/group_vars/all/compute
@@ -16,7 +16,7 @@ compute_network_interfaces: >
 
 # List of default networks to which compute nodes are attached.
 compute_default_network_interfaces: >
-  {{ ([provision_oc_net_name,
+  {{ ([admin_oc_net_name,
        internal_net_name,
        storage_net_name] +
       (external_net_names if kolla_enable_neutron_provider_networks | bool else [])) | unique | list }}
diff --git a/ansible/group_vars/all/controllers b/ansible/group_vars/all/controllers
index 16ae8e549a29c7ee93d60ad21c9c18e896a7db45..360d9dba7c90dd1c7d318e8502ea4ceb49ce6e9c 100644
--- a/ansible/group_vars/all/controllers
+++ b/ansible/group_vars/all/controllers
@@ -18,7 +18,7 @@ controller_network_interfaces: >
 
 # List of default networks to which controller nodes are attached.
 controller_default_network_interfaces: >
-  {{ [provision_oc_net_name,
+  {{ [admin_oc_net_name,
       oob_wl_net_name,
       provision_wl_net_name,
       inspection_net_name,
diff --git a/ansible/group_vars/all/monitoring b/ansible/group_vars/all/monitoring
index fbf20ed69bef43ec275efb144b4a65fd53f3ab8b..4174b25258b5f55a3ae2826a2cfe313f577f457d 100644
--- a/ansible/group_vars/all/monitoring
+++ b/ansible/group_vars/all/monitoring
@@ -18,7 +18,7 @@ monitoring_network_interfaces: >
 
 # List of default networks to which monitoring nodes are attached.
 monitoring_default_network_interfaces: >
-  {{ [provision_oc_net_name,
+  {{ [admin_oc_net_name,
       internal_net_name,
       public_net_name] | unique | list }}
 
diff --git a/ansible/group_vars/all/network b/ansible/group_vars/all/network
index 3597cf3d650e949c25ee400f56ddc19fd2d94602..affb8ffdcc6f3d3a702cd5a64f740908b80fc35a 100644
--- a/ansible/group_vars/all/network
+++ b/ansible/group_vars/all/network
@@ -2,6 +2,9 @@
 ###############################################################################
 # Network role to network name mappings.
 
+# Name of the network used for admin access to the overcloud
+admin_oc_net_name: "{{ provision_oc_net_name }}"
+
 # Name of the network used by the overcloud hosts to manage the bare metal
 # compute hosts via their out-of-band management controllers.
 oob_oc_net_name: 'oob_oc_net'
diff --git a/ansible/group_vars/all/seed b/ansible/group_vars/all/seed
index fec668f1056e725de7e5b383a46c7ca746da149c..cc59a73a7ed5cdfcecf4c37aed94067a2152ca11 100644
--- a/ansible/group_vars/all/seed
+++ b/ansible/group_vars/all/seed
@@ -16,7 +16,8 @@ seed_network_interfaces: >
 
 # List of default networks to which seed nodes are attached.
 seed_default_network_interfaces: >
-  {{ [oob_oc_net_name,
+  {{ [admin_oc_net_name,
+      oob_oc_net_name,
       provision_oc_net_name] | unique | list }}
 
 # List of extra networks to which seed nodes are attached.
diff --git a/ansible/group_vars/all/storage b/ansible/group_vars/all/storage
index 6a60113f856217d4a5ff1fce719362ae19c41fcd..94317397ba6064e47ecccc77fc1140a0a16bb3b3 100644
--- a/ansible/group_vars/all/storage
+++ b/ansible/group_vars/all/storage
@@ -16,7 +16,7 @@ storage_network_interfaces: >
 
 # List of default networks to which storage nodes are attached.
 storage_default_network_interfaces: >
-  {{ [provision_oc_net_name,
+  {{ [admin_oc_net_name,
       internal_net_name,
       storage_mgmt_net_name,
       storage_net_name] | unique | list }}
diff --git a/ansible/group_vars/overcloud/ansible-host b/ansible/group_vars/overcloud/ansible-host
index df6d5357598518ccaf4f08f57d6441150b0a83c2..8456343e21dde84afaa621e90c5d532b34573ee6 100644
--- a/ansible/group_vars/overcloud/ansible-host
+++ b/ansible/group_vars/overcloud/ansible-host
@@ -1,3 +1,3 @@
 ---
 # Host/IP with which to access the overcloud nodes via SSH.
-ansible_host: "{{ provision_oc_net_name | net_ip }}"
+ansible_host: "{{ admin_oc_net_name | net_ip }}"
diff --git a/ansible/group_vars/seed-hypervisor/ansible-host b/ansible/group_vars/seed-hypervisor/ansible-host
index 29c6f548b0837fdc93bf213c5a28ff97dd8db606..a1eeaac8b59a5e9fae8dffafe6d5cf0250ae38e4 100644
--- a/ansible/group_vars/seed-hypervisor/ansible-host
+++ b/ansible/group_vars/seed-hypervisor/ansible-host
@@ -1,3 +1,3 @@
 ---
 # Host/IP with which to access the seed hypervisor via SSH.
-ansible_host: "{{ provision_oc_net_name | net_ip }}"
+ansible_host: "{{ admin_oc_net_name | net_ip }}"
diff --git a/ansible/group_vars/seed/ansible-host b/ansible/group_vars/seed/ansible-host
index bb6cb26ac6e277a7fa856e212d40a06a33940588..d3078dc1716f3596a3962dbfe773a8e4f6412f59 100644
--- a/ansible/group_vars/seed/ansible-host
+++ b/ansible/group_vars/seed/ansible-host
@@ -1,3 +1,3 @@
 ---
 # Host/IP with which to access the seed via SSH.
-ansible_host: "{{ provision_oc_net_name | net_ip }}"
+ansible_host: "{{ admin_oc_net_name | net_ip }}"
diff --git a/ansible/kolla-bifrost-hostvars.yml b/ansible/kolla-bifrost-hostvars.yml
index 7364578b59588bbc7d5028fcffc2b9902b301ecb..ad3d746c1b60e9870b3554124c6dbc5ae2ce070b 100644
--- a/ansible/kolla-bifrost-hostvars.yml
+++ b/ansible/kolla-bifrost-hostvars.yml
@@ -30,17 +30,17 @@
   vars:
     seed_host: "{{ groups['seed'][0] }}"
     bifrost_hostvars:
-      # Also supports vlan_id and network_mtu.
       addressing_mode: static
       ipv4_interface_mac: "{% raw %}{{ extra.pxe_interface_mac | default }}{% endraw %}"
-      ipv4_address: "{{ provision_oc_net_name | net_ip }}"
-      ipv4_subnet_mask: "{{ provision_oc_net_name | net_cidr | ipaddr('netmask') }}"
-      # If the provisioning network does not have a gateway defined, use the
+      ipv4_address: "{{ admin_oc_net_name | net_ip }}"
+      ipv4_subnet_mask: "{{ admin_oc_net_name | net_cidr | ipaddr('netmask') }}"
+      # If the admin network does not have a gateway defined, use the
       # seed as a gateway to allow external access until other networks have
       # been configured.
-      ipv4_gateway: "{{ provision_oc_net_name | net_gateway or provision_oc_net_name | net_ip(seed_host) }}"
+      ipv4_gateway: "{{ admin_oc_net_name | net_gateway or admin_oc_net_name | net_ip(seed_host) }}"
       ipv4_nameserver: "{{ resolv_nameservers }}"
-      network_mtu: "{{ provision_oc_net_name | net_mtu or '1500' }}"
+      network_mtu: "{{ admin_oc_net_name | net_mtu or '1500' }}"
+      vlan_id: "{{ '' if admin_oc_net_name == provision_oc_net_name else (admin_oc_net_name | net_vlan) }}"
   tasks:
     - name: Ensure the Bifrost host variable files exist
       copy:
diff --git a/ansible/overcloud-etc-hosts-fixup.yml b/ansible/overcloud-etc-hosts-fixup.yml
index 64e26b4c3da887a10db807f0bb755cd957975f46..6c154622264f6d899237dce6a599c918cf60304f 100644
--- a/ansible/overcloud-etc-hosts-fixup.yml
+++ b/ansible/overcloud-etc-hosts-fixup.yml
@@ -1,29 +1,29 @@
 ---
 # For some currently unknown reason, overcloud hosts end up with multiple
-# entries in /etc/hosts that map their own hostname to their provisioning
+# entries in /etc/hosts that map their own hostname to their admin
 # network IP address, in addition to one that maps their own hostname to their
 # internal network IP address. This causes RabbitMQ upgrades to fail, as
 # RabbitMQ expects the system's hostname to resolve to the IP address on
 # which it is listening. As a workaround, we remove the stale entries from
 # /etc/hosts.  See https://github.com/stackhpc/kayobe/issues/14.
 
-- name: Ensure overcloud hosts' /etc/hosts does not contain provisioning network IP
+- name: Ensure overcloud hosts' /etc/hosts does not contain admin network IP
   hosts: overcloud
   tags:
     - etc-hosts-fixup
   tasks:
-    - name: Ensure overcloud hosts' /etc/hosts does not contain provisioning network or loopback IPs
+    - name: Ensure overcloud hosts' /etc/hosts does not contain admin network or loopback IPs
       lineinfile:
         dest: /etc/hosts
         regexp: "^{{ item }}[ \t]*{{ inventory_hostname }}"
         state: absent
       with_items:
         - "127.0.0.1"
-        - "{{ provision_oc_net_name | net_ip }}"
-      when: provision_oc_net_name | net_ip != None
+        - "{{ admin_oc_net_name | net_ip }}"
+      when: admin_oc_net_name | net_ip != None
       become: True
 
-- name: Ensure rabbitmq containers' /etc/hosts does not contain provisioning network or loopback IPs
+- name: Ensure rabbitmq containers' /etc/hosts does not contain admin network or loopback IPs
   hosts: overcloud
   tags:
     - etc-hosts-fixup
@@ -40,7 +40,7 @@
           with_items: "{{ rabbitmq_containers }}"
           register: ps_result
 
-        - name: Ensure rabbitmq containers' /etc/hosts does not contain provisioning network or loopback IPs
+        - name: Ensure rabbitmq containers' /etc/hosts does not contain admin network or loopback IPs
           command: >
             docker exec -u root {{ item.0.item }}
             bash -c
@@ -55,7 +55,7 @@
           with_nested:
             - "{{ ps_result.results }}"
             - - "127.0.0.1"
-              - "{{ provision_oc_net_name | net_ip }}"
+              - "{{ admin_oc_net_name | net_ip }}"
           when: item.0.rc == 0
           register: sed_result
-      when: provision_oc_net_name | net_ip != None
+      when: admin_oc_net_name | net_ip != None
diff --git a/doc/source/configuration/network.rst b/doc/source/configuration/network.rst
index 000d6c2d0a5d582202a9f8d48d11c027e382ac4f..b5957356e528d7c3ff6d90b58a57dedf17e66c2d 100644
--- a/doc/source/configuration/network.rst
+++ b/doc/source/configuration/network.rst
@@ -398,6 +398,9 @@ In order to provide flexibility in the system's network topology, Kayobe maps
 the named networks to logical network roles.  A single named network may
 perform multiple roles, or even none at all.  The available roles are:
 
+Overcloud admin network (``admin_oc_net_name``)
+    Name of the network used to access the overcloud for admin purposes, e.g
+    for remote SSH access.
 Overcloud out-of-band network (``oob_oc_net_name``)
     Name of the network used by the seed to access the out-of-band management
     controllers of the bare metal overcloud hosts.
@@ -440,6 +443,7 @@ To configure network roles in a system with two networks, ``example1`` and
 .. code-block:: yaml
    :caption: ``networks.yml``
 
+   admin_oc_net_name: example1
    oob_oc_net_name: example1
    provision_oc_net_name: example1
    oob_wl_net_name: example1
@@ -452,6 +456,31 @@ To configure network roles in a system with two networks, ``example1`` and
    inspection_net_name: example2
    cleaning_net_name: example2
 
+Overcloud Admin Network
+-----------------------
+
+The admin network is intended to be used for remote access to the overcloud hosts.
+Kayobe will use the address assigned to the host on this network as the
+``ansible_host`` when executing playbooks. It is therefore a necessary requirement
+to configure this network.
+
+By default Kayobe will use the overcloud provisioning network as the admin network.
+It is, however, possible to configure a separate network. To do so, you should
+override ``admin_oc_net_name`` in your networking configuration.
+
+If a separate network is configured, the following requirements should be taken into
+consideration:
+
+* The admin network must be configured to use the same physical network interface
+  as the provisioning network. This is because the PXE MAC address is used to
+  lookup the interface for the cloud-init network configuration that occurs
+  during bifrost provisioning of the overcloud.
+
+* If the admin network is configured as a tagged VLAN, you must configure Kayobe
+  to upgrade cloud-init. This is a temporary workaround for a bug in the current
+  version of cloud-init shipped with CentOS 7.5. Please see :ref:`workaround-cloud-init`
+  for more details.
+
 Overcloud Provisioning Network
 ------------------------------
 
@@ -593,6 +622,7 @@ Seed
 
 By default, the seed is attached to the following networks:
 
+* overcloud admin network
 * overcloud out-of-band network
 * overcloud provisioning network
 
@@ -617,7 +647,7 @@ Controllers
 
 By default, controllers are attached to the following networks:
 
-* overcloud provisioning network
+* overcloud admin network
 * workload (compute) out-of-band network
 * workload (compute) provisioning network
 * workload (compute) inspection network
@@ -645,7 +675,7 @@ controllers when they are in the ``controllers`` group.  If the monitoring
 hosts are not in the ``controllers`` group, they are attached to the following
 networks by default:
 
-* overcloud provisioning network
+* overcloud admin network
 * internal network
 * public network
 
@@ -659,7 +689,7 @@ Virtualised Compute Hosts
 
 By default, virtualised compute hosts are attached to the following networks:
 
-* overcloud provisioning network
+* overcloud admin network
 * internal network
 * storage network
 
diff --git a/doc/source/deployment.rst b/doc/source/deployment.rst
index 3e1df7909fcb706997f6fa49c97ac151a72b5cae..b2a57a8d6fba2048e4c1a61511239a523bb6b40d 100644
--- a/doc/source/deployment.rst
+++ b/doc/source/deployment.rst
@@ -133,6 +133,8 @@ image name regular expressions::
 In order to push images to a registry after they are built, add the ``--push``
 argument.
 
+.. _workaround-cloud-init:
+
 Workaround VLAN cloud-init issue
 --------------------------------
 
diff --git a/etc/kayobe/networks.yml b/etc/kayobe/networks.yml
index 9e10b851b7f36b11ae544259d10667f04931cb93..fdc1420e1184bea00caa23eb7a881c30ff33f642 100644
--- a/etc/kayobe/networks.yml
+++ b/etc/kayobe/networks.yml
@@ -4,6 +4,9 @@
 ###############################################################################
 # Network role to network mappings.
 
+# Name of the network used for admin access to the overcloud
+#admin_oc_net_name:
+
 # Name of the network used by the seed to manage the bare metal overcloud
 # hosts via their out-of-band management controllers.
 #oob_oc_net_name:
@@ -50,6 +53,15 @@
 ###############################################################################
 # Network definitions.
 
+# Admin network IP information.
+# admin_oc_net_cidr:
+# admin_oc_net_allocation_pool_start:
+# admin_oc_net_allocation_pool_end:
+# admin_oc_net_gateway:
+# admin_oc_net_vlan:
+# admin_oc_net_mtu:
+# admin_oc_net_routes:
+
 # Overcloud out-of-band management network IP information.
 # oob_oc_net_cidr:
 # oob_oc_net_allocation_pool_start:
diff --git a/releasenotes/notes/add-admin-network.yml-cf4b5e6387d0eb3f.yaml b/releasenotes/notes/add-admin-network.yml-cf4b5e6387d0eb3f.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c3ecada60cc4913444e9a2189e294e0cd0f45aa4
--- /dev/null
+++ b/releasenotes/notes/add-admin-network.yml-cf4b5e6387d0eb3f.yaml
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds a new overcloud admin network to improve network separation.
+    The network is intended to be used for remote admin access e.g SSH.
+    If ``admin_oc_net_name`` is not set, Kayobe will use the previous
+    behaviour of using the overcloud provisioning network for this purpose.
+    See `Story 2002096
+    <https://storyboard.openstack.org/#!/story/2002096>`_