Skip to content

Instantly share code, notes, and snippets.

Last active January 13, 2024 23:40
Show Gist options
  • Star 97 You must be signed in to star a gist
  • Fork 40 You must be signed in to fork a gist
  • Save samhocevar/00eec26d9e9988d080ac to your computer and use it in GitHub Desktop.
Save samhocevar/00eec26d9e9988d080ac to your computer and use it in GitHub Desktop.
Configure sshd on MSYS2 and run it as a Windows service
# — configure sshd on MSYS2 and run it as a Windows service
# Please report issues and/or improvements to Sam Hocevar <>
# Prerequisites:
# — MSYS2 itself:
# — admin tools: pacman -S openssh cygrunsrv mingw-w64-x86_64-editrights
# This script is a cleaned up and improved version of the procedure initially
# found at
# Gotchas:
# — the log file will be /var/log/msys2_sshd.log
# — if you get error “sshd: fatal: seteuid XXX : No such device or address”
# in the logs, try “passwd -R” (with admin privileges)
# Changelog:
# 27 Jun 2019 — rename service to msys2_sshd to avoid conflicts with Windows OpenSSH
# — use mkgroup.exe as suggested in the comments
# — fix a problem with CRLF and grep
# 24 Aug 2015 — run server with -e to redirect logs to /var/log/sshd.log
set -e
# Configuration
PRIV_NAME="Privileged user for sshd"
UNPRIV_USER=sshd # DO NOT CHANGE; this username is hardcoded in the openssh code
UNPRIV_NAME="Privilege separation user for sshd"
# Check installation sanity
if ! /mingw64/bin/editrights -h >/dev/null; then
echo "ERROR: Missing 'editrights'. Try: pacman -S mingw-w64-x86_64-editrights."
exit 1
if ! cygrunsrv -v >/dev/null; then
echo "ERROR: Missing 'cygrunsrv'. Try: pacman -S cygrunsrv."
exit 1
if ! ssh-keygen -A; then
echo "ERROR: Missing 'ssh-keygen'. Try: pacman -S openssh."
exit 1
# The privileged cyg_server user
# Some random password; this is only needed internally by cygrunsrv and
# is limited to 14 characters by Windows (lol)
tmp_pass="$(tr -dc 'a-zA-Z0-9' < /dev/urandom | dd count=14 bs=1 2>/dev/null)"
# Create user
add="$(if ! net user "${PRIV_USER}" >/dev/null; then echo "//add"; fi)"
if ! net user "${PRIV_USER}" "${tmp_pass}" ${add} //fullname:"${PRIV_NAME}" \
//homedir:"$(cygpath -w ${EMPTY_DIR})" //yes; then
echo "ERROR: Unable to create Windows user ${PRIV_USER}"
exit 1
# Add user to the Administrators group if necessary
admingroup="$(mkgroup -l | awk -F: '{if ($2 == "S-1-5-32-544") print $1;}')"
if ! (net localgroup "${admingroup}" | grep -q '^'"${PRIV_USER}"'\>'); then
if ! net localgroup "${admingroup}" "${PRIV_USER}" //add; then
echo "ERROR: Unable to add user ${PRIV_USER} to group ${admingroup}"
exit 1
# Infinite passwd expiry
passwd -e "${PRIV_USER}"
# set required privileges
for flag in SeAssignPrimaryTokenPrivilege SeCreateTokenPrivilege \
SeTcbPrivilege SeDenyRemoteInteractiveLogonRight SeServiceLogonRight; do
if ! /mingw64/bin/editrights -a "${flag}" -u "${PRIV_USER}"; then
echo "ERROR: Unable to give ${flag} rights to user ${PRIV_USER}"
exit 1
# The unprivileged sshd user (for privilege separation)
add="$(if ! net user "${UNPRIV_USER}" >/dev/null; then echo "//add"; fi)"
if ! net user "${UNPRIV_USER}" ${add} //fullname:"${UNPRIV_NAME}" \
//homedir:"$(cygpath -w ${EMPTY_DIR})" //active:no; then
echo "ERROR: Unable to create Windows user ${PRIV_USER}"
exit 1
# Add or update /etc/passwd entries
touch /etc/passwd
for u in "${PRIV_USER}" "${UNPRIV_USER}"; do
sed -i -e '/^'"${u}"':/d' /etc/passwd
mkpasswd -l -u "${u}" | sed -e 's/^[^:]*+//' | sed -ne "${SED}" \
>> /etc/passwd
mkgroup.exe -l > /etc/group
# Finally, register service with cygrunsrv and start it
cygrunsrv -R msys2_sshd || true
cygrunsrv -I msys2_sshd -d "MSYS2 sshd" -p \
/usr/bin/sshd.exe -a "-D -e" -y tcpip -u "${PRIV_USER}" -w "${tmp_pass}"
# The SSH service should start automatically when Windows is rebooted. You can
# manually restart the service by running `net stop msys2_sshd` + `net start msys2_sshd`
if ! net start msys2_sshd; then
echo "ERROR: Unable to start msys2_sshd service"
exit 1
Copy link

dgleba commented Aug 18, 2021

thanks! this worked for me.

Copy link

dmikushin commented Nov 25, 2021

There is an important information that should be added somewhere (but I don't know where): This configuration of sshd only works for local user. It's not possible to login as domain user (this produces the seteuid fatal error) and I haven't found the missing bits and pieces (passwd -R isn't enough).

@kolewu Thanks for a very helpful note. Apparently this is solved in

Copy link

I've tried several different versions of this script including this one,, and All have issues.

When I run this script I get the below error:
"The user name could not be found.

More help is available by typing NET HELPMSG 2221.

The option //ADD is unknown.

The syntax of this command is:

[username [password | *] [options]] [/DOMAIN]
username {password | *} /ADD [options] [/DOMAIN]
username [/DELETE] [/DOMAIN]
username [/TIMES:{times | ALL}]
username [/ACTIVE: {YES | NO}]

More help is available by typing NET HELPMSG 3506.

ERROR: Unable to create Windows user "

How do I get around this? I tried jouven's instructions. I ran /usr/bin/mkpasswd | grep using my personal account and copied that line into /etc/passwd. I already have a password on my account, but still tried passwd and it said "You may not change the password for ?. I thought that was fine so I continued on and ran the script and got the error above. Any ideas? Thanks

Copy link

@wapiti96 The script uses //ADD to tell the msys2 path handling converter to not treat it as a path and convert it to /ADD.

Make sure you are not setting the environment variable MSYS2_ARG_CONV_EXCL or other variables related to path conversion.

Copy link

if you want to execute a windows app say notepad you are running windows service you have to use a weird hack like this

MSYS2_ARG_CONV_EXCL="*" schtasks /create /sc once /tn "NotepadIn10Sec" /tr "notepad.exe" /st  $(date -d @"$((current_time=$(date +%s) +40 ))" +"%H:%M:%S")  && ( sleep 50;  MSYS2_ARG_CONV_EXCL="*" schtasks /delete /tn "NotepadIn10Sec" /f)

assuming you're logged in as the same user

Copy link

and in case you're on a firewall:

cygrunsrv -R msys2_autossh ; cygrunsrv -I msys2_autossh -d "MSYS2 autossh" -p "/usr/bin/bash.exe" -a "autossh -M 0 -i /home/myuser/.ssh/id_rsa -o UserKnownHostsFile=/home/myuser/.ssh/known_hosts -R 9090:localhost:22 remoteuser@ -N" -y msys2_sshd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment