diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index 90495fa7a2..4ee40b4042 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -164,6 +164,7 @@ nova_api_port: "8774"
 nova_metadata_port: "8775"
 nova_novncproxy_port: "6080"
 nova_spicehtml5proxy_port: "6082"
+nova_serialproxy_port: "6083"
 
 neutron_server_port: "9696"
 
@@ -324,6 +325,7 @@ enable_neutron_lbaas: "no"
 enable_neutron_fwaas: "no"
 enable_neutron_qos: "no"
 enable_neutron_agent_ha: "no"
+enable_nova_serialconsole_proxy: "no"
 enable_octavia: "no"
 enable_panko: "no"
 enable_rally: "no"
diff --git a/ansible/inventory/all-in-one b/ansible/inventory/all-in-one
index 5daa04f1a0..889786dfb5 100644
--- a/ansible/inventory/all-in-one
+++ b/ansible/inventory/all-in-one
@@ -199,6 +199,9 @@ nova
 [nova-compute-ironic:children]
 nova
 
+[nova-serialproxy:children]
+nova
+
 # Neutron
 [neutron-server:children]
 control
diff --git a/ansible/inventory/multinode b/ansible/inventory/multinode
index 129883e9de..5ab14b697c 100644
--- a/ansible/inventory/multinode
+++ b/ansible/inventory/multinode
@@ -215,6 +215,9 @@ nova
 [nova-compute-ironic:children]
 nova
 
+[nova-serialproxy:children]
+nova
+
 # Neutron
 [neutron-server:children]
 control
diff --git a/ansible/roles/haproxy/tasks/precheck.yml b/ansible/roles/haproxy/tasks/precheck.yml
index 90d3d3b99e..fc34b6dde2 100644
--- a/ansible/roles/haproxy/tasks/precheck.yml
+++ b/ansible/roles/haproxy/tasks/precheck.yml
@@ -355,6 +355,18 @@
     - nova_console == 'novnc'
     - inventory_hostname in groups['haproxy']
 
+- name: Checking free port for Nova Serial Proxy HAProxy
+  wait_for:
+    host: "{{ kolla_internal_vip_address }}"
+    port: "{{ nova_serialproxy_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - enable_nova | bool
+    - "{{ 'nova_serialconsole_proxy' not in haproxy_stat }}"
+    - enable_nova_serialconsole_proxy | bool
+    - inventory_hostname in groups['haproxy']
+
 - name: Checking free port for Nova Spice HTML5 HAProxy
   wait_for:
     host: "{{ kolla_internal_vip_address }}"
diff --git a/ansible/roles/haproxy/templates/haproxy.cfg.j2 b/ansible/roles/haproxy/templates/haproxy.cfg.j2
index 91157020d0..db2b142f2f 100644
--- a/ansible/roles/haproxy/templates/haproxy.cfg.j2
+++ b/ansible/roles/haproxy/templates/haproxy.cfg.j2
@@ -130,6 +130,14 @@ listen nova_spicehtml5proxy
   server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ nova_spicehtml5proxy_port }} check inter 2000 rise 2 fall 5
 {% endfor %}
 {% endif %}
+
+{% if enable_nova_serialconsole_proxy | bool %}
+listen nova_serialconsole_proxy
+  bind {{ kolla_internal_vip_address }}:{{ nova_serialproxy_port }}
+{% for host in groups['nova-serialproxy'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + api_interface]['ipv4']['address'] }}:{{ nova_serialproxy_port }} check inter 2000 rise 2 fall 5
+{% endfor %}
+{% endif %}
 {% if haproxy_enable_external_vip | bool %}
 
 listen nova_api_external
@@ -165,6 +173,16 @@ listen nova_spicehtml5proxy_external
   server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ nova_spicehtml5proxy_port }} check inter 2000 rise 2 fall 5
 {% endfor %}
 {% endif %}
+
+{% if enable_nova_serialconsole_proxy | bool %}
+listen nova_serialconsole_proxy_external
+  bind {{ kolla_external_vip_address }}:{{ nova_serialproxy_port }} {{ tls_bind_info }}
+  http-request del-header X-Forwarded-Proto
+  http-request set-header X-Forwarded-Proto https if { ssl_fc }
+{% for host in groups['nova-serialproxy'] %}
+  server {{ hostvars[host]['ansible_hostname'] }} {{ hostvars[host]['ansible_' + hostvars[host]['api_interface']]['ipv4']['address'] }}:{{ nova_serialproxy_port }} check inter 2000 rise 2 fall 5
+{% endfor %}
+{% endif %}
 {% endif %}
 {% endif %}
 
diff --git a/ansible/roles/nova/defaults/main.yml b/ansible/roles/nova/defaults/main.yml
index bf24c02f78..669caf9fd9 100644
--- a/ansible/roles/nova/defaults/main.yml
+++ b/ansible/roles/nova/defaults/main.yml
@@ -72,6 +72,10 @@ nova_compute_ironic_image: "{{ docker_registry ~ '/' if docker_registry else ''
 nova_compute_ironic_tag: "{{ openstack_release }}"
 nova_compute_ironic_image_full: "{{ nova_compute_ironic_image }}:{{ nova_compute_ironic_tag }}"
 
+nova_serialproxy_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ kolla_install_type }}-nova-serialproxy"
+nova_serialproxy_tag: "{{ openstack_release }}"
+nova_serialproxy_image_full: "{{ nova_serialproxy_image }}:{{ nova_serialproxy_tag }}"
+
 ####################
 # OpenStack
 ####################
diff --git a/ansible/roles/nova/tasks/config.yml b/ansible/roles/nova/tasks/config.yml
index e7c7a06e3a..da2192252a 100644
--- a/ansible/roles/nova/tasks/config.yml
+++ b/ansible/roles/nova/tasks/config.yml
@@ -26,6 +26,7 @@
     - "nova-scheduler"
     - "nova-spicehtml5proxy"
     - "nova-ssh"
+    - "nova-serialproxy"
 
 - name: Copying over config.json files for services
   template:
@@ -42,6 +43,7 @@
     - "nova-scheduler"
     - "nova-spicehtml5proxy"
     - "nova-ssh"
+    - "nova-serialproxy"
 
 - name: Copying over nova.conf
   merge_configs:
@@ -65,6 +67,7 @@
     - "nova-novncproxy"
     - "nova-scheduler"
     - "nova-spicehtml5proxy"
+    - "nova-serialproxy"
 
 - name: Copying over libvirt configuration
   template:
@@ -101,5 +104,6 @@
     - "nova-novncproxy"
     - "nova-scheduler"
     - "nova-spicehtml5proxy"
+    - "nova-serialproxy"
   when:
     nova_policy.stat.exists
diff --git a/ansible/roles/nova/tasks/deploy.yml b/ansible/roles/nova/tasks/deploy.yml
index 21efc2b2e1..dd699eef07 100644
--- a/ansible/roles/nova/tasks/deploy.yml
+++ b/ansible/roles/nova/tasks/deploy.yml
@@ -24,7 +24,8 @@
         inventory_hostname in groups['nova-conductor'] or
         inventory_hostname in groups['nova-consoleauth'] or
         inventory_hostname in groups['nova-novncproxy'] or
-        inventory_hostname in groups['nova-scheduler']
+        inventory_hostname in groups['nova-scheduler'] or
+        inventory_hostname in groups['nova-serialproxy']
 
 - include: config-nova-fake.yml
   when:
@@ -41,4 +42,6 @@
         inventory_hostname in groups['nova-conductor'] or
         inventory_hostname in groups['nova-consoleauth'] or
         inventory_hostname in groups['nova-novncproxy'] or
-        inventory_hostname in groups['nova-scheduler']
+        inventory_hostname in groups['nova-scheduler'] or
+        inventory_hostname in groups['nova-serialproxy']
+
diff --git a/ansible/roles/nova/tasks/precheck.yml b/ansible/roles/nova/tasks/precheck.yml
index 5edcaa26c8..430dd19cd6 100644
--- a/ansible/roles/nova/tasks/precheck.yml
+++ b/ansible/roles/nova/tasks/precheck.yml
@@ -4,6 +4,7 @@
     name:
       - nova_api
       - nova_novncproxy
+      - nova_serialproxy
       - nova_spicehtml5proxy
   register: container_facts
 
@@ -38,6 +39,17 @@
     - nova_console == 'novnc'
     - inventory_hostname in groups['nova-novncproxy']
 
+- name: Checking free port for Nova Serial Proxy
+  wait_for:
+    host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
+    port: "{{ nova_serialproxy_port }}"
+    connect_timeout: 1
+    state: stopped
+  when:
+    - container_facts['nova_serialproxy'] is not defined
+    - enable_nova_serialconsole_proxy | bool
+    - inventory_hostname in groups['nova-serialproxy']
+
 - name: Checking free port for Nova Spice HTML5 Proxy
   wait_for:
     host: "{{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}"
diff --git a/ansible/roles/nova/tasks/pull.yml b/ansible/roles/nova/tasks/pull.yml
index ec5f6af936..0d03e77145 100644
--- a/ansible/roles/nova/tasks/pull.yml
+++ b/ansible/roles/nova/tasks/pull.yml
@@ -68,6 +68,15 @@
     image: "{{ nova_scheduler_image_full }}"
   when: inventory_hostname in groups['nova-scheduler']
 
+- name: Pulling nova-serialproxy image
+  kolla_docker:
+    action: "pull_image"
+    common_options: "{{ docker_common_options }}"
+    image: "{{ nova_serialproxy_image_full }}"
+  when:
+    - inventory_hostname in groups['nova-serialproxy']
+    - enable_nova_serialconsole_proxy | bool
+
 - name: Pulling nova-spicehtml5proxy image
   kolla_docker:
     action: "pull_image"
diff --git a/ansible/roles/nova/tasks/reconfigure.yml b/ansible/roles/nova/tasks/reconfigure.yml
index b551dd92ac..a78074c303 100644
--- a/ansible/roles/nova/tasks/reconfigure.yml
+++ b/ansible/roles/nova/tasks/reconfigure.yml
@@ -54,6 +54,16 @@
     - nova_console == 'spice'
     - inventory_hostname in groups['nova-spicehtml5proxy']
 
+- name: Ensuring the nova_serialproxy container is up
+  kolla_docker:
+    name: "nova_serialproxy"
+    action: "get_container_state"
+  register: container_state
+  failed_when: container_state.Running == false
+  when:
+    - enable_nova_serialconsole_proxy | bool
+    - inventory_hostname in groups['nova-serialproxy']
+
 - include: config.yml
 
 - name: Check the configs for nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
@@ -106,6 +116,15 @@
     - nova_console == 'spice'
     - inventory_hostname in groups['nova-spicehtml5proxy']
 
+- name: Check the configs in the nova_serialproxy container
+  command: docker exec nova_serialproxy /usr/local/bin/kolla_set_configs --check
+  changed_when: false
+  failed_when: false
+  register: nova_serialproxy_check_result
+  when:
+    - enable_nova_serialconsole_proxy | bool
+    - inventory_hostname in groups['nova-serialproxy']
+
 # NOTE(jeffrey4l): when config_strategy == 'COPY_ALWAYS'
 # and container env['KOLLA_CONFIG_STRATEGY'] == 'COPY_ONCE',
 # just remove the container and start again
@@ -159,6 +178,15 @@
     - nova_console == 'spice'
     - inventory_hostname in groups['nova-spicehtml5proxy']
 
+- name: Container config strategy for nova_serialproxy
+  kolla_docker:
+    name: nova_serialproxy
+    action: "get_container_env"
+  register: nova_serialproxy_container_env
+  when:
+    - enable_nova_serialconsole_proxy | bool
+    - inventory_hostname in groups['nova-serialproxy']
+
 - name: Remove the nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
   kolla_docker:
     name: "{{ item[0]['name'] }}"
@@ -226,6 +254,17 @@
     - config_strategy == 'COPY_ONCE' or nova_spicehtml5proxy_container_env['KOLLA_CONFIG_STRATEGY'] == 'COPY_ONCE'
     - nova_spicehtml5proxy_check_result['rc'] == 1
 
+- name: Remove nova_serialproxy container
+  kolla_docker:
+    name: nova_serialproxy
+    action: "remove_container"
+  register: remove_nova_serialproxy_container
+  when:
+    - enable_nova_serialconsole_proxy | bool
+    - inventory_hostname in groups['nova-serialproxy']
+    - config_strategy == 'COPY_ONCE' or nova_serialproxy_container_env['KOLLA_CONFIG_STRATEGY'] == 'COPY_ONCE'
+    - nova_serialproxy_check_result['rc'] == 1
+
 - include: start.yml
   when: remove_containers.changed
 
@@ -254,6 +293,11 @@
     - nova_console == 'spice'
     - remove_nova_spicehtml5proxy_container.changed
 
+- include: start.yml
+  when:
+    - enable_nova_serialconsole_proxy | bool
+    - remove_nova_serialproxy_container.changed
+
 - name: Restart the nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
   kolla_docker:
     name: "{{ item[0]['name'] }}"
@@ -316,3 +360,14 @@
     - inventory_hostname in groups['nova-spicehtml5proxy']
     - nova_spicehtml5proxy_container_env['KOLLA_CONFIG_STRATEGY'] != 'COPY_ONCE'
     - nova_spicehtml5proxy_check_result['rc'] == 1
+
+- name: Restart the nova_serialproxy container
+  kolla_docker:
+    name: "nova_serialproxy"
+    action: "restart_container"
+  when:
+    - enable_nova_serialconsole_proxy | bool
+    - config_strategy == 'COPY_ALWAYS'
+    - inventory_hostname in groups['nova-serialproxy']
+    - nova_serialproxy_container_env['KOLLA_CONFIG_STRATEGY'] != 'COPY_ONCE'
+    - nova_serialproxy_check_result['rc'] == 1
diff --git a/ansible/roles/nova/tasks/start_controllers.yml b/ansible/roles/nova/tasks/start_controllers.yml
index 184927281a..0159226175 100644
--- a/ansible/roles/nova/tasks/start_controllers.yml
+++ b/ansible/roles/nova/tasks/start_controllers.yml
@@ -51,6 +51,20 @@
       - "kolla_logs:/var/log/kolla/"
   when: inventory_hostname in groups['nova-scheduler']
 
+- name: Starting nova-serialproxy container
+  kolla_docker:
+    action: "start_container"
+    common_options: "{{ docker_common_options }}"
+    image: "{{ nova_serialproxy_image_full }}"
+    name: "nova_serialproxy"
+    volumes:
+      - "{{ node_config_directory }}/nova-serialproxy/:{{ container_config_directory }}/:ro"
+      - "/etc/localtime:/etc/localtime:ro"
+      - "kolla_logs:/var/log/kolla/"
+  when:
+    - inventory_hostname in groups['nova-serialproxy']
+    - enable_nova_serialconsole_proxy | bool
+
 - name: Starting nova-spicehtml5proxy container
   kolla_docker:
     action: "start_container"
diff --git a/ansible/roles/nova/templates/nova-serialproxy.json.j2 b/ansible/roles/nova/templates/nova-serialproxy.json.j2
new file mode 100644
index 0000000000..3aac725913
--- /dev/null
+++ b/ansible/roles/nova/templates/nova-serialproxy.json.j2
@@ -0,0 +1,18 @@
+{
+    "command": "nova-serialproxy",
+    "config_files": [
+        {
+            "source": "{{ container_config_directory }}/nova.conf",
+            "dest": "/etc/nova/nova.conf",
+            "owner": "nova",
+            "perm": "0600"
+        }
+    ],
+    "permissions": [
+        {
+            "path": "/var/log/kolla/nova",
+            "owner": "nova:nova",
+            "recurse": true
+        }
+    ]
+}
diff --git a/ansible/roles/nova/templates/nova.conf.j2 b/ansible/roles/nova/templates/nova.conf.j2
index 383192c72a..d729fa37c3 100644
--- a/ansible/roles/nova/templates/nova.conf.j2
+++ b/ansible/roles/nova/templates/nova.conf.j2
@@ -87,6 +87,14 @@ html5proxy_base_url = {{ public_protocol }}://{% if orchestration_engine == 'KUB
 html5proxy_host = {{ api_interface_address }}
 html5proxy_port = {{ nova_spicehtml5proxy_port }}
 {% endif %}
+{% if enable_nova_serialconsole_proxy | bool %}
+[serial_console]
+enabled = true
+base_url = ws://{{ kolla_external_fqdn }}:{{ nova_serialproxy_port }}/
+serialproxy_host = {{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}
+serialproxy_port = {{ nova_serialproxy_port }}
+proxyclient_address = {{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}
+{% endif %}
 
 {% if service_name == "nova-compute-ironic" %}
 [ironic]
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 2fea2e80c4..ebcced2078 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -169,6 +169,7 @@ kolla_internal_vip_address: "10.10.10.254"
 #enable_neutron_qos: "no"
 #enable_neutron_agent_ha: "no"
 #enable_neutron_vpnaas: "no"
+#enable_nova_serialconsole_proxy: "no"
 #enable_octavia: "no"
 #enable_panko: "no"
 #enable_rally: "no"