---
title: Disable SSH Root Login
description: Datadog, the leading service for cloud-scale monitoring.
breadcrumbs: Docs > Datadog Security > OOTB Rules > Disable SSH Root Login
---

> For the complete documentation index, see [llms.txt](https://docs.datadoghq.com/llms.txt).

# Disable SSH Root Login
 
## Description{% #description %}

The root user should never be allowed to login to a system directly over a network. To disable root login via SSH, add or correct the following line in `/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf`:

```
PermitRootLogin no
```

## Rationale{% #rationale %}

Even though the communications channel may be encrypted, an additional layer of security is gained by extending the policy of not logging directly on as root. In addition, logging in with a user-specific account provides individual accountability of actions performed on the system and also helps to minimize direct attack attempts on root's password.

## Remediation{% #remediation %}

### Shell script{% #shell-script %}

The following script can be run on the host to remediate the issue.

```bash
#!/bin/bash

# Remediation is applicable only in certain platforms
if dpkg-query --show --showformat='${db:Status-Status}' 'linux-base' 2>/dev/null | grep -q '^installed$'; then

mkdir -p /etc/ssh/sshd_config.d
chmod 0700 /etc/ssh/sshd_config.d
touch /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
chmod 0600 /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config"
LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config.d"/*.conf
# sshd reads /etc/ssh/sshd_config.d through an Include line of /etc/ssh/sshd_config.
if grep -Eiqs '^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config; then
    if [ -e "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" ] ; then
        LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    else
        touch "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
        chmod 0644 "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"

    cp "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf" "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
    # Insert at the beginning of the file
    printf '%s\n' "PermitRootLogin no" > "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    cat "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak" >> "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.d/00-complianceascode-hardening.conf.bak"
else
    if [ -e "/etc/ssh/sshd_config" ] ; then
        LC_ALL=C sed -i "/^\s*PermitRootLogin\s\+/Id" "/etc/ssh/sshd_config"
    else
        touch "/etc/ssh/sshd_config"
        chmod 0600 "/etc/ssh/sshd_config"
    fi
    # make sure file has newline at the end
    sed -i -e '$a\' "/etc/ssh/sshd_config"

    cp "/etc/ssh/sshd_config" "/etc/ssh/sshd_config.bak"
    # Insert at the beginning of the file
    printf '%s\n' "PermitRootLogin no" > "/etc/ssh/sshd_config"
    cat "/etc/ssh/sshd_config.bak" >> "/etc/ssh/sshd_config"
    # Clean up after ourselves.
    rm "/etc/ssh/sshd_config.bak"
fi

else
    >&2 echo 'Remediation is not applicable, nothing was done'
fi
```

### Ansible playbook{% #ansible-playbook %}

The following playbook can be run with Ansible to remediate the issue.

```go
- name: Gather the package facts
  package_facts:
    manager: auto
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Find if a Match block of /etc/ssh/sshd_config sets
    the parameter PermitRootLogin
  ansible.builtin.find:
    paths:
    - /etc/ssh
    patterns: sshd_config
    contains: (?ims)^[ \t]*Match[ \t].*^[ \t]*PermitRootLogin[ \t]
    read_whole_file: true
  register: _sshd_main_config_match_sets_parameter
  when: '"linux-base" in ansible_facts.packages'
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Remove the parameter PermitRootLogin from the Match
    blocks of /etc/ssh/sshd_config
  ansible.builtin.replace:
    path: '{{ item.path }}'
    after: (?im)^[ \t]*Match[ \t]
    regexp: (?i)^[ \t]*PermitRootLogin[ \t].*\n?
    replace: ''
  loop: '{{ _sshd_main_config_match_sets_parameter.files }}'
  when: '"linux-base" in ansible_facts.packages'
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Find the files of /etc/ssh/sshd_config.d where a
    Match block sets the parameter PermitRootLogin
  ansible.builtin.find:
    paths:
    - /etc/ssh/sshd_config.d
    patterns: '*.conf'
    contains: (?ims)^[ \t]*Match[ \t].*^[ \t]*PermitRootLogin[ \t]
    read_whole_file: true
  register: _sshd_config_dir_match_sets_parameter
  when: '"linux-base" in ansible_facts.packages'
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Remove the parameter PermitRootLogin from the Match
    blocks of the files of /etc/ssh/sshd_config.d
  ansible.builtin.replace:
    path: '{{ item.path }}'
    after: (?im)^[ \t]*Match[ \t]
    regexp: (?i)^[ \t]*PermitRootLogin[ \t].*\n?
    replace: ''
  loop: '{{ _sshd_config_dir_match_sets_parameter.files }}'
  when: '"linux-base" in ansible_facts.packages'
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Check if /etc/ssh/sshd_config includes /etc/ssh/sshd_config.d
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: (?i)^\s*Include\s+/etc/ssh/sshd_config\.d/\*\.conf
    state: absent
  check_mode: true
  changed_when: false
  failed_when: false
  register: _sshd_config_includes_config_dir
  when: '"linux-base" in ansible_facts.packages'
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Set the parameter PermitRootLogin in /etc/ssh/sshd_config.d
  block:

  - name: Disable SSH Root Login - Check if the parameter PermitRootLogin is configured
      in /etc/ssh/sshd_config
    ansible.builtin.lineinfile:
      path: /etc/ssh/sshd_config
      regexp: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
      state: absent
    check_mode: true
    changed_when: false
    register: _config_file_has_parameter

  - name: Disable SSH Root Login - Check if the parameter PermitRootLogin is configured
      in /etc/ssh/sshd_config.d
    ansible.builtin.find:
      paths:
      - /etc/ssh/sshd_config.d
      contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
      patterns: '*.conf'
    register: _config_dir_has_parameter

  - name: Disable SSH Root Login - Count the lines that configure the parameter PermitRootLogin
      in /etc/ssh/sshd_config.d
    ansible.builtin.lineinfile:
      path: '{{ item.path }}'
      regexp: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
      state: absent
    check_mode: true
    changed_when: false
    loop: '{{ _config_dir_has_parameter.files }}'
    register: _config_dir_parameter_lines

  - name: Disable SSH Root Login - Check if /etc/ssh/sshd_config exists
    ansible.builtin.stat:
      path: /etc/ssh/sshd_config
    register: _config_file_exists

  - name: Disable SSH Root Login - Check if the parameter PermitRootLogin is configured
      correctly in /etc/ssh/sshd_config
    ansible.builtin.lineinfile:
      path: /etc/ssh/sshd_config
      regexp: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+no$
      state: absent
    check_mode: true
    changed_when: false
    register: _config_file_correctly
    when: _config_file_exists.stat.exists

  - name: Disable SSH Root Login - Check if the parameter PermitRootLogin is configured
      correctly in /etc/ssh/sshd_config.d
    ansible.builtin.find:
      paths:
      - /etc/ssh/sshd_config.d
      contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+no$
      patterns: '*.conf'
    register: _config_dir_correctly

  - name: Disable SSH Root Login
    block:

    - name: Deduplicate values from /etc/ssh/sshd_config
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        create: false
        regexp: (?i)(?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
        mode: '0600'
        state: absent

    - name: Check if /etc/ssh/sshd_config.d exists
      ansible.builtin.stat:
        path: /etc/ssh/sshd_config.d
      register: _etc_ssh_sshd_config_d_exists

    - name: Check if the parameter PermitRootLogin is present in /etc/ssh/sshd_config.d
      ansible.builtin.find:
        paths: /etc/ssh/sshd_config.d
        recurse: 'yes'
        follow: 'no'
        contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
        patterns: '*.conf'
      register: _etc_ssh_sshd_config_d_has_parameter
      when: _etc_ssh_sshd_config_d_exists.stat.isdir is defined and _etc_ssh_sshd_config_d_exists.stat.isdir

    - name: Remove parameter from files in /etc/ssh/sshd_config.d
      ansible.builtin.lineinfile:
        path: '{{ item.path }}'
        create: false
        regexp: (?i)(?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
        mode: '0600'
        state: absent
      with_items: '{{ _etc_ssh_sshd_config_d_has_parameter.files | default([]) }}'
      when: _etc_ssh_sshd_config_d_has_parameter.matched > 0

    - name: Insert correct line to /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
        create: true
        regexp: (?i)(?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
        mode: '0600'
        line: PermitRootLogin no
        state: present
        insertbefore: BOF
        validate: /usr/sbin/sshd -t -f %s
    when:
    - _config_file_correctly is not skipped
    - (_config_file_correctly.found == 0 and _config_dir_correctly.matched == 0) or
      ((_config_file_has_parameter.found | int) + (_config_dir_parameter_lines.results
      | map(attribute='found') | map('int') | sum)) != 1

  - name: Disable SSH Root Login - set file mode for /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
    ansible.builtin.file:
      path: /etc/ssh/sshd_config.d/00-complianceascode-hardening.conf
      mode: '0600'
      state: touch
      modification_time: preserve
      access_time: preserve
  when:
  - '"linux-base" in ansible_facts.packages'
  - (_sshd_config_includes_config_dir.found | default(0) | int) > 0
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login

- name: Disable SSH Root Login - Set the parameter PermitRootLogin in /etc/ssh/sshd_config
  block:

  - name: Disable SSH Root Login - Find the files of /etc/ssh/sshd_config.d that set
      the parameter PermitRootLogin
    ansible.builtin.find:
      paths:
      - /etc/ssh/sshd_config.d
      patterns: '*.conf'
      contains: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
    register: _sshd_config_dir_sets_parameter

  - name: Disable SSH Root Login - Remove the parameter PermitRootLogin from the files
      of /etc/ssh/sshd_config.d
    ansible.builtin.lineinfile:
      path: '{{ item.path }}'
      regexp: (?i)^\s*{{ "PermitRootLogin"| regex_escape }}\s+
      state: absent
    loop: '{{ _sshd_config_dir_sets_parameter.files }}'

  - name: Disable SSH Root Login
    block:

    - name: Check for duplicate values
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        create: true
        regexp: (?i)(?i)^\s*PermitRootLogin\s+
        mode: '0600'
        state: absent
      check_mode: true
      changed_when: false
      register: dupes

    - name: Deduplicate values from /etc/ssh/sshd_config
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        create: true
        regexp: (?i)(?i)^\s*PermitRootLogin\s+
        mode: '0600'
        state: absent
      when: dupes.found is defined and dupes.found > 1

    - name: Insert correct line to /etc/ssh/sshd_config
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        create: true
        regexp: (?i)(?i)^\s*PermitRootLogin\s+
        mode: '0600'
        line: PermitRootLogin no
        state: present
        insertbefore: BOF
        validate: /usr/sbin/sshd -t -f %s
  when:
  - '"linux-base" in ansible_facts.packages'
  - (_sshd_config_includes_config_dir.found | default(0) | int) == 0
  tags:
  - CJIS-5.5.6
  - NIST-800-53-AC-17(a)
  - NIST-800-53-AC-6(2)
  - NIST-800-53-CM-6(a)
  - NIST-800-53-CM-7(a)
  - NIST-800-53-CM-7(b)
  - NIST-800-53-IA-2
  - NIST-800-53-IA-2(5)
  - PCI-DSS-Req-2.2.4
  - PCI-DSSv4-2.2
  - PCI-DSSv4-2.2.6
  - low_complexity
  - low_disruption
  - medium_severity
  - no_reboot_needed
  - restrict_strategy
  - sshd_disable_root_login
```
