diff --git a/files/gitlab-config-template/gitlab.rb.template b/files/gitlab-config-template/gitlab.rb.template
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvbmZpZy10ZW1wbGF0ZS9naXRsYWIucmIudGVtcGxhdGU=..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvbmZpZy10ZW1wbGF0ZS9naXRsYWIucmIudGVtcGxhdGU= 100644
--- a/files/gitlab-config-template/gitlab.rb.template
+++ b/files/gitlab-config-template/gitlab.rb.template
@@ -1138,6 +1138,36 @@
 ##! **We do not recommend changing this directory.**
 # gitlab_shell['dir'] = "/var/opt/gitlab/gitlab-shell"
 
+################################################################################
+## gitlab-sshd
+################################################################################
+
+# gitlab_sshd['enable'] = false
+# gitlab_sshd['generate_host_keys'] = true
+# gitlab_sshd['dir'] = "/var/opt/gitlab/gitlab-sshd"
+
+# gitlab-sshd outputs most logs to /var/log/gitlab/gitlab-shell/gitlab-shell.log.
+# This directory only stores stdout/stderr output from the daemon.
+# gitlab_sshd['log_directory'] = "/var/log/gitlab/gitlab-sshd/"
+
+# gitlab_sshd['env_directory'] = '/opt/gitlab/etc/gitlab-sshd/env'
+# gitlab_sshd['listen_address'] = 'localhost:2222'
+# gitlab_sshd['metrics_address'] = 'localhost:9122'
+# gitlab_sshd['concurrent_sessions_limit'] = 100
+# gitlab_sshd['proxy_protocol'] = false
+# gitlab_sshd['proxy_policy'] = 'use'
+# gitlab_sshd['proxy_header_timeout'] = '500ms'
+# gitlab_sshd['grace_period'] = 55
+# gitlab_sshd['client_alive_interval'] = nil
+# gitlab_sshd['ciphers'] = nil
+# gitlab_sshd['kex_algorithms'] = nil
+# gitlab_sshd['macs'] = nil
+# gitlab_sshd['login_grace_time'] = 60
+# gitlab_sshd['host_keys_dir'] = '/var/opt/gitlab/gitlab-sshd'
+# gitlab_sshd['host_keys_glob'] = 'ssh_host_*_key'
+# gitlab_sshd['host_certs_dir'] = '/var/opt/gitlab/gitlab-sshd'
+# gitlab_sshd['host_certs_glob'] = 'ssh_host_*-cert.pub'
+
 ################################################################
 ## GitLab PostgreSQL
 ################################################################
diff --git a/files/gitlab-cookbooks/gitlab/attributes/default.rb b/files/gitlab-cookbooks/gitlab/attributes/default.rb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvYXR0cmlidXRlcy9kZWZhdWx0LnJi..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvYXR0cmlidXRlcy9kZWZhdWx0LnJi 100644
--- a/files/gitlab-cookbooks/gitlab/attributes/default.rb
+++ b/files/gitlab-cookbooks/gitlab/attributes/default.rb
@@ -614,6 +614,33 @@
   "default" => { "path" => "/var/opt/gitlab/git-data" }
 }
 
+###
+# gitlab-sshd
+###
+default['gitlab']['gitlab_sshd']['enable'] = false
+default['gitlab']['gitlab_sshd']['generate_host_keys'] = true
+default['gitlab']['gitlab_sshd']['dir'] = "/var/opt/gitlab/gitlab-sshd"
+# gitlab-sshd outputs most logs to /var/log/gitlab/gitlab-shell/gitlab-shell.log.
+# This directory only stores any stdout/stderr output from the daemon.
+default['gitlab']['gitlab_sshd']['log_directory'] = "/var/log/gitlab/gitlab-sshd/"
+default['gitlab']['gitlab_sshd']['env_directory'] = '/opt/gitlab/etc/gitlab-sshd/env'
+default['gitlab']['gitlab_sshd']['listen_address'] = 'localhost:2222'
+default['gitlab']['gitlab_sshd']['metrics_address'] = 'localhost:9122'
+default['gitlab']['gitlab_sshd']['concurrent_sessions_limit'] = 100
+default['gitlab']['gitlab_sshd']['proxy_protocol'] = false
+default['gitlab']['gitlab_sshd']['proxy_policy'] = 'use'
+default['gitlab']['gitlab_sshd']['proxy_header_timeout'] = '500ms'
+default['gitlab']['gitlab_sshd']['grace_period'] = 55
+default['gitlab']['gitlab_sshd']['client_alive_interval'] = nil
+default['gitlab']['gitlab_sshd']['ciphers'] = nil
+default['gitlab']['gitlab_sshd']['kex_algorithms'] = nil
+default['gitlab']['gitlab_sshd']['macs'] = nil
+default['gitlab']['gitlab_sshd']['login_grace_time'] = 60
+default['gitlab']['gitlab_sshd']['host_keys_dir'] = '/var/opt/gitlab/gitlab-sshd'
+default['gitlab']['gitlab_sshd']['host_keys_glob'] = 'ssh_host_*_key'
+default['gitlab']['gitlab_sshd']['host_certs_dir'] = '/var/opt/gitlab/gitlab-sshd'
+default['gitlab']['gitlab_sshd']['host_certs_glob'] = 'ssh_host_*-cert.pub'
+
 ####
 # Web server
 ####
diff --git a/files/gitlab-cookbooks/gitlab/libraries/gitlab_sshd_helper.rb b/files/gitlab-cookbooks/gitlab/libraries/gitlab_sshd_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvbGlicmFyaWVzL2dpdGxhYl9zc2hkX2hlbHBlci5yYg==
--- /dev/null
+++ b/files/gitlab-cookbooks/gitlab/libraries/gitlab_sshd_helper.rb
@@ -0,0 +1,55 @@
+class GitlabSshdHelper
+  OMNIBUS_KEYS = %w[enable dir generate_host_keys log_directory env_directory host_keys_dir host_keys_glob host_certs_dir host_certs_glob].freeze
+
+  def initialize(node)
+    @node = node
+  end
+
+  # This returns the configuration needed to start gitlab-sshd inside the gitlab-shell
+  # configuration file.
+  #
+  # We purposely don't memoize this call since find_host_keys! and find_host_certs!
+  # may change if new host keys are created.
+  def json_config
+    config = @node['gitlab']['gitlab_sshd'].dup
+
+    find_host_keys!(config)
+    find_host_certs!(config)
+
+    config['listen'] = config.delete('listen_address')
+    config['web_listen'] = config.delete('metrics_address')
+    OMNIBUS_KEYS.each { |key| config.delete(key) }
+
+    config
+  end
+
+  def no_host_keys?
+    json_config['host_key_files'].empty?
+  end
+
+  private
+
+  def find_host_keys!(config)
+    host_keys_dir = config['host_keys_dir']
+    host_keys_glob = config['host_keys_glob']
+
+    return unless host_keys_glob && host_keys_dir
+
+    path = File.join(host_keys_dir, host_keys_glob)
+
+    host_keys = Dir[path]
+    config['host_key_files'] = host_keys
+  end
+
+  def find_host_certs!(config)
+    host_certs_dir = config['host_certs_dir']
+    host_certs_glob = config['host_certs_glob']
+
+    return unless host_certs_glob && host_certs_dir
+
+    path = File.join(host_certs_dir, host_certs_glob)
+
+    host_certs = Dir[path]
+    config['host_key_certs'] = host_certs
+  end
+end
diff --git a/files/gitlab-cookbooks/gitlab/recipes/gitlab-shell.rb b/files/gitlab-cookbooks/gitlab/recipes/gitlab-shell.rb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItc2hlbGwucmI=..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItc2hlbGwucmI= 100644
--- a/files/gitlab-cookbooks/gitlab/recipes/gitlab-shell.rb
+++ b/files/gitlab-cookbooks/gitlab/recipes/gitlab-shell.rb
@@ -16,6 +16,8 @@
 ##
 #
 account_helper = AccountHelper.new(node)
+omnibus_helper = OmnibusHelper.new(node)
+sshd_helper = GitlabSshdHelper.new(node)
 
 git_user = account_helper.gitlab_user
 git_group = account_helper.gitlab_group
@@ -26,6 +28,14 @@
 log_directory = node['gitlab']['gitlab-shell']['log_directory']
 gitlab_shell_config_file = File.join(gitlab_shell_var_dir, "config.yml")
 
+gitlab_sshd_enabled = Services.enabled?('gitlab_sshd')
+gitlab_sshd_generate_host_keys = node['gitlab']['gitlab_sshd']['generate_host_keys']
+gitlab_sshd_bin_path = File.join(gitlab_shell_dir, 'bin', 'gitlab-sshd')
+gitlab_sshd_working_dir = node['gitlab']['gitlab_sshd']['dir']
+gitlab_sshd_log_dir = node['gitlab']['gitlab_sshd']['log_directory']
+gitlab_sshd_env_dir = node['gitlab']['gitlab_sshd']['env_directory']
+gitlab_sshd_host_key_dir = node['gitlab']['gitlab_sshd']['host_keys_dir']
+
 # Creates `.ssh` directory to hold authorized_keys
 [
   ssh_dir,
@@ -38,5 +48,5 @@
   end
 end
 
-[
+directories = [
   log_directory,
@@ -42,6 +52,10 @@
   log_directory,
-  gitlab_shell_var_dir
-].each do |dir|
+  gitlab_shell_var_dir,
+]
+
+directories += [gitlab_sshd_working_dir, gitlab_sshd_log_dir] if gitlab_sshd_enabled
+
+directories.each do |dir|
   directory dir do
     owner git_user
     mode "0700"
@@ -50,6 +64,25 @@
 end
 
 gitlab_url, gitlab_relative_path = WebServerHelper.internal_api_url(node)
+ssh_key_path = File.join(gitlab_sshd_host_key_dir, '/etc/ssh')
+ssh_key_glob = File.join(ssh_key_path, 'ssh_host_*_key')
+
+bash 'generate gitlab-sshd host keys' do
+  action
+  only_if { gitlab_sshd_enabled && gitlab_sshd_generate_host_keys && Dir.exist?(gitlab_sshd_host_key_dir) && sshd_helper.no_host_keys? }
+  code <<~EOS
+    set -e
+    mkdir -p #{ssh_key_path}
+    ssh-keygen -A -f #{gitlab_sshd_host_key_dir}
+    chmod 0600 #{ssh_key_glob}
+    chown #{git_user}:#{git_group} #{ssh_key_glob}
+    mv #{ssh_key_glob} #{gitlab_sshd_host_key_dir}
+  EOS
+end
+
+gitlab_rails_dir = node['gitlab']['gitlab-rails']['dir']
+gitlab_rails_etc_dir = File.join(gitlab_rails_dir, "etc")
+gitlab_shell_secret_file = File.join(gitlab_rails_etc_dir, 'gitlab_shell_secret')
 
 templatesymlink "Create a config.yml and create a symlink to Rails root" do
   link_from File.join(gitlab_shell_dir, "config.yml")
@@ -58,18 +91,24 @@
   mode "0640"
   owner "root"
   group git_group
-  variables({
-              user: git_user,
-              gitlab_url: gitlab_url,
-              gitlab_relative_path: gitlab_relative_path,
-              authorized_keys: authorized_keys,
-              log_file: File.join(log_directory, "gitlab-shell.log"),
-              log_level: node['gitlab']['gitlab-shell']['log_level'],
-              log_format: node['gitlab']['gitlab-shell']['log_format'],
-              audit_usernames: node['gitlab']['gitlab-shell']['audit_usernames'],
-              http_settings: node['gitlab']['gitlab-shell']['http_settings'],
-              git_trace_log_file: node['gitlab']['gitlab-shell']['git_trace_log_file'],
-              migration: node['gitlab']['gitlab-shell']['migration'],
-              ssl_cert_dir: node['gitlab']['gitlab-shell']['ssl_cert_dir']
-            })
+  variables(
+    lazy do
+      {
+        user: git_user,
+        gitlab_url: gitlab_url,
+        gitlab_relative_path: gitlab_relative_path,
+        authorized_keys: authorized_keys,
+        secret_file: gitlab_shell_secret_file,
+        log_file: File.join(log_directory, "gitlab-shell.log"),
+        log_level: node['gitlab']['gitlab-shell']['log_level'],
+        log_format: node['gitlab']['gitlab-shell']['log_format'],
+        audit_usernames: node['gitlab']['gitlab-shell']['audit_usernames'],
+        http_settings: node['gitlab']['gitlab-shell']['http_settings'],
+        git_trace_log_file: node['gitlab']['gitlab-shell']['git_trace_log_file'],
+        migration: node['gitlab']['gitlab-shell']['migration'],
+        ssl_cert_dir: node['gitlab']['gitlab-shell']['ssl_cert_dir'],
+        gitlab_sshd: gitlab_sshd_enabled ? sshd_helper.json_config : nil
+      }
+    end
+  )
   notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
@@ -75,4 +114,5 @@
   notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
+  notifies :restart, "runit_service[gitlab-sshd]" if gitlab_sshd_enabled
   sensitive true
 end
 
@@ -87,3 +127,22 @@
   action :create_if_missing
   notifies :run, 'bash[Set proper security context on ssh files for selinux]', :delayed if SELinuxHelper.enabled?
 end
+
+env_dir gitlab_sshd_env_dir do
+  action gitlab_sshd_enabled ? :create : :nothing
+  variables node['gitlab']['gitlab_sshd']['env']
+  notifies :restart, "runit_service[gitlab-sshd]" if omnibus_helper.should_notify?('gitlab_sshd')
+end
+
+runit_service 'gitlab-sshd' do
+  action gitlab_sshd_enabled ? :enable : :disable
+  options({
+    user: account_helper.gitlab_user,
+    groupname: account_helper.gitlab_group,
+    working_dir: gitlab_sshd_working_dir,
+    env_dir: gitlab_sshd_env_dir,
+    bin_path: gitlab_sshd_bin_path,
+    config_dir: gitlab_shell_var_dir,
+    log_directory: gitlab_sshd_log_dir,
+  }.merge(params))
+end
diff --git a/files/gitlab-cookbooks/gitlab/templates/default/gitlab-shell-config.yml.erb b/files/gitlab-cookbooks/gitlab/templates/default/gitlab-shell-config.yml.erb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvdGVtcGxhdGVzL2RlZmF1bHQvZ2l0bGFiLXNoZWxsLWNvbmZpZy55bWwuZXJi..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvdGVtcGxhdGVzL2RlZmF1bHQvZ2l0bGFiLXNoZWxsLWNvbmZpZy55bWwuZXJi 100644
--- a/files/gitlab-cookbooks/gitlab/templates/default/gitlab-shell-config.yml.erb
+++ b/files/gitlab-cookbooks/gitlab/templates/default/gitlab-shell-config.yml.erb
@@ -12,6 +12,10 @@
 gitlab_relative_url_root: <%= @gitlab_relative_path %>
 <% end %>
 
+# File that contains the secret key for verifying access to GitLab.
+# Default is .gitlab_shell_secret in the gitlab-shell directory.
+secret_file: "<%= @secret_file %>"
+
 http_settings:
   <%= @http_settings.to_json if @http_settings %>
 #  user: someone
@@ -55,3 +59,7 @@
 # It needs to exist so we can check permissions and avoid to throwing warnings to the users.
 git_trace_log_file: "<%= @git_trace_log_file %>"
 <% end %>
+
+<% if @gitlab_sshd %>
+sshd: <%= @gitlab_sshd.to_json %>
+<% end %>
diff --git a/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-log-config.erb b/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-log-config.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvdGVtcGxhdGVzL2RlZmF1bHQvc3YtZ2l0bGFiLXNzaGQtbG9nLWNvbmZpZy5lcmI=
--- /dev/null
+++ b/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-log-config.erb
@@ -0,0 +1,6 @@
+<%= "s#@svlogd_size" if @svlogd_size %>
+<%= "n#@svlogd_num" if @svlogd_num %>
+<%= "t#@svlogd_timeout" if @svlogd_timeout %>
+<%= "!#@svlogd_filter" if @svlogd_filter %>
+<%= "u#@svlogd_udp" if @svlogd_udp %>
+<%= "p#@svlogd_prefix" if @svlogd_prefix %>
diff --git a/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-log-run.erb b/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-log-run.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvdGVtcGxhdGVzL2RlZmF1bHQvc3YtZ2l0bGFiLXNzaGQtbG9nLXJ1bi5lcmI=
--- /dev/null
+++ b/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-log-run.erb
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec svlogd <%= @options[:log_directory] %>
diff --git a/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-run.erb b/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-run.erb
new file mode 100644
index 0000000000000000000000000000000000000000..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9naXRsYWIvdGVtcGxhdGVzL2RlZmF1bHQvc3YtZ2l0bGFiLXNzaGQtcnVuLmVyYg==
--- /dev/null
+++ b/files/gitlab-cookbooks/gitlab/templates/default/sv-gitlab-sshd-run.erb
@@ -0,0 +1,13 @@
+#!/bin/bash
+set -e # fail on errors
+
+# Redirect stderr -> stdout
+exec 2>&1
+
+cd <%= @options[:working_dir] %>
+
+exec chpst -P -e <%= @options[:env_dir] %> \
+-U <%= @options[:user] %>:<%= @options[:groupname] %> \
+-u <%= @options[:user] %>:<%= @options[:groupname] %> \
+<%= @options[:bin_path] %> \
+  -config-dir <%= @options[:config_dir] %> \
diff --git a/files/gitlab-cookbooks/package/libraries/config/gitlab.rb b/files/gitlab-cookbooks/package/libraries/config/gitlab.rb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvZ2l0bGFiLnJi..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvZ2l0bGFiLnJi 100644
--- a/files/gitlab-cookbooks/package/libraries/config/gitlab.rb
+++ b/files/gitlab-cookbooks/package/libraries/config/gitlab.rb
@@ -91,6 +91,7 @@
     ee_attribute('suggested_reviewers').use { SuggestedReviewers }
 
     # Base GitLab attributes
+    attribute('gitlab_sshd',      priority: 5)
     attribute('gitlab_shell',     priority: 10).use { GitlabShell } # Parse shell before rails for data dir settings
     attribute('gitlab_rails',     priority: 15).use { GitlabRails } # Parse rails first as others may depend on it
     attribute('gitlab_workhorse', priority: 20).use { GitlabWorkhorse }
diff --git a/files/gitlab-cookbooks/package/libraries/config/services.rb b/files/gitlab-cookbooks/package/libraries/config/services.rb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvc2VydmljZXMucmI=..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9jb25maWcvc2VydmljZXMucmI= 100644
--- a/files/gitlab-cookbooks/package/libraries/config/services.rb
+++ b/files/gitlab-cookbooks/package/libraries/config/services.rb
@@ -43,6 +43,7 @@
     service 'storage_check'
     service 'crond'
     service 'praefect'
+    service 'gitlab_sshd'
   end
 
   # Define the services included in the EE edition of GitLab
diff --git a/files/gitlab-cookbooks/package/libraries/settings_dsl.rb b/files/gitlab-cookbooks/package/libraries/settings_dsl.rb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZXR0aW5nc19kc2wucmI=..f67e6a508af155548a72d71a27f3820c6092150f_ZmlsZXMvZ2l0bGFiLWNvb2tib29rcy9wYWNrYWdlL2xpYnJhcmllcy9zZXR0aW5nc19kc2wucmI= 100644
--- a/files/gitlab-cookbooks/package/libraries/settings_dsl.rb
+++ b/files/gitlab-cookbooks/package/libraries/settings_dsl.rb
@@ -251,7 +251,7 @@
       def sanitized_key(key)
         # Services that have been migrated to use underscored form in Chef code
         # and no longer needs to be hyphenated.
-        skip_hyphenation = %w[gitlab_pages]
+        skip_hyphenation = %w[gitlab_pages gitlab_sshd]
 
         return underscored_form(key) if skip_hyphenation.include?(underscored_form(key))
 
diff --git a/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb b/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb
index db8b43c93038a5c7fd29aac666c9e50a50e57fbc_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItc2hlbGxfc3BlYy5yYg==..f67e6a508af155548a72d71a27f3820c6092150f_c3BlYy9jaGVmL2Nvb2tib29rcy9naXRsYWIvcmVjaXBlcy9naXRsYWItc2hlbGxfc3BlYy5yYg== 100644
--- a/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb
+++ b/spec/chef/cookbooks/gitlab/recipes/gitlab-shell_spec.rb
@@ -1,7 +1,7 @@
 require 'chef_helper'
 
 RSpec.describe 'gitlab::gitlab-shell' do
-  let(:chef_run) { ChefSpec::SoloRunner.converge('gitlab::default') }
+  let(:chef_run) { ChefSpec::SoloRunner.new(step_into: %w(templatesymlink runit_service)).converge('gitlab::default') }
 
   before do
     allow(Gitlab).to receive(:[]).and_call_original
@@ -45,8 +45,9 @@
     it 'create config file in default location with default values' do
       expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
         hash_including(
+          secret_file: '/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret',
           log_file: '/var/log/gitlab/gitlab-shell/gitlab-shell.log',
           log_format: "json",
           migration: { enabled: true, features: [] },
           gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
           gitlab_relative_path: '',
@@ -48,9 +49,10 @@
           log_file: '/var/log/gitlab/gitlab-shell/gitlab-shell.log',
           log_format: "json",
           migration: { enabled: true, features: [] },
           gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
           gitlab_relative_path: '',
-          ssl_cert_dir: '/opt/gitlab/embedded/ssl/certs/'
+          ssl_cert_dir: '/opt/gitlab/embedded/ssl/certs/',
+          gitlab_sshd: nil
         )
       )
     end
@@ -54,6 +56,21 @@
         )
       )
     end
+
+    it 'renders gitlab-shell config' do
+      expect(chef_run).to render_file('/var/opt/gitlab/gitlab-shell/config.yml').with_content { |content|
+        data = YAML.safe_load(content)
+
+        expect(data['secret_file']).to eq('/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret')
+        expect(data['log_file']).to eq('/var/log/gitlab/gitlab-shell/gitlab-shell.log')
+        expect(data['log_format']).to eq('json')
+        expect(data['migration']).to eq({ "enabled" => true, "features" => [] })
+        expect(data['gitlab_url']).to eq('http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket')
+        expect(data['gitlab_relative_path']).to be_nil
+        expect(data['ssl_cert_dir']).to eq('/opt/gitlab/embedded/ssl/certs/')
+        expect(data['sshd']).to be_nil
+      }
+    end
   end
 
   context 'with a non-default directory' do
@@ -226,7 +243,8 @@
         expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
           hash_including(
             gitlab_url: 'http://localhost:1234/gitlab',
-            gitlab_relative_path: nil
+            gitlab_relative_path: nil,
+            secret_file: '/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret'
           )
         )
       end
@@ -241,7 +259,8 @@
         expect(chef_run).to create_templatesymlink('Create a config.yml and create a symlink to Rails root').with_variables(
           hash_including(
             gitlab_url: 'http+unix://%2Fvar%2Fopt%2Fgitlab%2Fgitlab-workhorse%2Fsockets%2Fsocket',
-            gitlab_relative_path: '/gitlab'
+            gitlab_relative_path: '/gitlab',
+            secret_file: '/var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret'
           )
         )
       end
@@ -262,4 +281,95 @@
       end
     end
   end
+
+  context 'with gitlab-sshd enabled' do
+    let(:templatesymlink) { chef_run.templatesymlink('Create a config.yml and create a symlink to Rails root') }
+    let(:expected_sshd_keys) do
+      %w[listen proxy_protocol proxy_policy web_listen concurrent_sessions_limit client_alive_interval
+         grace_period login_grace_time proxy_header_timeout macs kex_algorithms ciphers host_key_files host_key_certs]
+    end
+
+    before do
+      stub_gitlab_rb(
+        gitlab_sshd: {
+          enable: true,
+          log_directory: '/tmp/log'
+        }
+      )
+    end
+
+    before do
+      allow(Dir).to receive(:[]).and_call_original
+    end
+
+    context 'with default host key and cert globs' do
+      let(:host_key) { %w(/tmp/host_keys/ssh_key) }
+      let(:default_host_key_glob) { '/var/opt/gitlab/gitlab-sshd/ssh_host_*_key' }
+      let(:default_host_cert_glob) { '/var/opt/gitlab/gitlab-sshd/ssh_host_*-cert.pub' }
+
+      before do
+        allow(Dir).to receive(:[]).with(default_host_key_glob).and_return(host_key)
+        allow(Dir).to receive(:[]).with(default_host_cert_glob).and_return([])
+      end
+
+      it 'renders gitlab-sshd config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-shell/config.yml').with_content { |content|
+          data = YAML.safe_load(content)
+
+          expect(data['sshd'].keys).to match_array(expected_sshd_keys)
+          expect(data['sshd']['host_key_files']).to eq(host_key)
+          expect(data['sshd']['host_key_certs']).to be_empty
+          expect(data['sshd']['listen']).to eq('localhost:2222')
+          expect(data['sshd']['web_listen']).to eq('localhost:9122')
+          expect(data['sshd']['proxy_protocol']).to be false
+        }
+      end
+
+      it 'template triggers notifications' do
+        expect(templatesymlink).to notify('runit_service[gitlab-sshd]').to(:restart).delayed
+      end
+
+      it 'correctly renders out the gitlab_sshd service file' do
+        expect(chef_run).to render_file("/opt/gitlab/sv/gitlab-sshd/run")
+          .with_content { |content|
+            expect(content).to include('cd /var/opt/gitlab/gitlab-sshd')
+            expect(content).to include('/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-sshd')
+            expect(content).to include('-config-dir /var/opt/gitlab/gitlab-shell')
+          }
+      end
+    end
+
+    context 'with custom host key path' do
+      let(:host_key_glob) { '/tmp/host_keys/ssh_key*' }
+      let(:host_cert_glob) { '/tmp/host_keys/ssh_cert*' }
+      let(:host_keys) { %w(/tmp/host_keys/ssh_key1 /tmp/host_keys/ssh_key2) }
+      let(:host_certs) { %w(/tmp/host_keys/ssh_cert1 /tmp/host_keys/ssh_cert2) }
+
+      before do
+        stub_gitlab_rb(
+          gitlab_sshd: {
+            enable: true,
+            log_directory: '/tmp/log',
+            host_keys_dir: '/tmp/host_keys',
+            host_keys_glob: 'ssh_key*',
+            host_certs_dir: '/tmp/host_keys',
+            host_certs_glob: 'ssh_cert*'
+          }
+        )
+
+        allow(Dir).to receive(:[]).with(host_key_glob).and_return(host_keys)
+        allow(Dir).to receive(:[]).with(host_cert_glob).and_return(host_certs)
+      end
+
+      it 'renders gitlab-sshd config' do
+        expect(chef_run).to render_file('/var/opt/gitlab/gitlab-shell/config.yml').with_content { |content|
+          data = YAML.safe_load(content)
+
+          expect(data['sshd'].keys).to match_array(expected_sshd_keys)
+          expect(data['sshd']['host_key_files']).to eq(host_keys)
+          expect(data['sshd']['host_key_certs']).to eq(host_certs)
+        }
+      end
+    end
+  end
 end