You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
How to get UTF-8 to work properly from a Linux SSH client #2468
This looks like a Windows-specific UTF-8/codepage handling issue for remote commands executed through sshd, so I’m marking it for maintainer investigation.
For maintainers
Windows-specific vs. parity: This appears Windows-specific rather than a general upstream OpenSSH bug. The reported output maps ä to CP437 (0x84) and the server side is OpenSSH_for_Windows running PowerShell 5.1 on Windows build 10.0.20348, so the likely fix area is Windows process/console/codepage handling in PowerShell/openssh-portable. Confidence: medium-high.
Reproducibility: The report includes a minimal command (ssh MyWindowsPC "echo ä"), expected/actual output, and OpenSSH_for_Windows version (9.5p2). It does not name the Linux client distribution/version or the exact remote shell/codepage settings, but the core repro is specific enough for initial investigation. Confidence: medium.
Copilot-fix suitability: Maybe. This may be localized if the problem is command encoding/codepage setup around Windows child process launch, but terminal/console encoding behavior can be subtle and may need maintainer validation across shells.
Likely area:Area-sshd and Area-Terminal because the symptom occurs when a Linux SSH client invokes a command on the Windows sshd server and receives escaped/non-UTF-8 output from the remote command path.
Related issues checked
Wrong locale settings used for error hints #2120 is open and also concerns UTF-8/locale escaping, but it is about localized error text from the Windows ssh client, not Linux-client-to-Windows-server remote command output.
It really depends on what the target default shell is and the configured console codepage. For example my target DefautlShell is powershell.exe so I need to explicitly change the codepage with chcp 65001 or [Console]::OutputEncoding = [Text.UTF8Encoding]::new() to tell PowerShell it can emit characters using UTF-8 bytes.
Windows does not set the console output codepage to UTF-8 by default for compatibility issues so you essentially need to tell it that UTF-8 is ok.
If your default shell is cmd (the default if not configured) then doing chcp 65001 && echo ä should work but I've not tested that.
I do see that when cmd is the default shell the chcp command didn't apply to the current process and I had to spawn a new one so it saw the new console codepage changes.
As for why quser still returns the \201 escape sequence I am not sure sorry. It could be that quser doesn't respect the console code page and always returns it in some other encoding. You could rule this out by opening cmd interactively on the Windows host and run the same chcp 65001 then the quser ... and redirect stdout/stderr to a file. From there you can inspect the raw bytes or use Format-Hex -Path ... in PowerShell to see whether the ü is UTF-8 encoded (quser respects the codepage) or something else (quser does not).
Ultimately this is a tricky thing, SSH just returns back the raw bytes it receives on the stdout/stderr so it is up to the applications that write to stdout/stderr to emit using the expected encoding. If there is a misalignment then you get issues like this where your client cannot render what the remote had emitted.
I think quser ignores the console's code page when redirecting to a file. Allegedly, other such 'old' command-line programs behave that way.
I am guessing that, when I connect from a Linux client, the SSH server (OpenSSH for Windows) creates a virtual console, but quser still feels it is not a real console, but some kind of redirection.
Even if I start a nested cmd (the trick explained above), it still does not work:
$ ssh MyRemoteHost "chcp 65001 && cmd /c \"quser /server:make-it-fail\""
Aktive Codepage: 65001.
Fehler 0x000006BA beim Auswerten der Sitzungsnamen
Fehler [1722]:Der RPC-Server ist nicht verf\201gbar.
Note the \201 again.
You can allegedly change the global default code page for such applications, but I do not really want to touch global settings on a server which I do not operate. Note that quser is a tool for Remote Desktop, so it is not an ancient legacy program.
I guess the only solution is to accept that quser generates text in code page 850, at least when running on European systems, and convert the text as necessary.
This is a general Windows problem, not really related to SSH. If a local Windows program wants to capture the text output from quser, it will have the same problem.
Therefore, unless somebody else has any other ideas, or maybe contradicting information (I am no expert here), I would close this bug report.
The options are as follows:
...
-t Force pseudo-terminal allocation. This can be used to
execute arbitrary screen-based programs on a remote
machine, which can be very useful, e.g. when implementing
menu services. Multiple -t options force tty allocation,
even if ssh has no local tty.
Thanks for the trick. Option '-t' does work better, characters like 'ä' are properly encoded.
The trouble is the side effect you mentioned. The console output you posted above does not show that the terminal gets cleared, which gives you a clue that something extra is going on.
I am redirecting the output of the whole ssh command to a file, in order to process the text given back, and then you see the terminal control codes in that file, and even cmd's prompt. For example command: ssh -t MyRemoteHost "echo ä && echo ä" >test.txt
yields this content in test.txt:
Where <ESC> is character code 0x1B and <BEL> is 0x07.
The pseudo-terminal seems to still be in code page 850, or at least that is what chcp reports when using "ssh -t". That also means that all Unicode characters which cannot be represented in 850 will probably get lost.
It looks like there is no easy, right way. No matter what option you choose, you have to add workarounds and/or live with lost Unicode characters.
Yes, I already mentioned that option further above. But this technique has its drawbacks: 1) On European servers the code page tends to be 850 instead of 437. As a client, you would have to find out first how the server is configured. 2) Unicode characters which do not fit the code page will get lost. 3) Some other command-line tools may actually honour the console code page, so this technique depends on the tool, and perhaps even on the tool version. It is all probably doable, but a big pain.
There is a significant difference depending on where the conversion takes place:
On the Windows side (you mentioned): it is difficult to ensure all tools use a consistent encoding, as some will inevitably continue to rely on the system code page.
On the Linux side (using iconv with a detected/peeked code page): while the tools on the Windows side rely on the system code page, everything is ultimately converted to UTF-8 once it reaches the Linux environment.
I would add some error detection, in case the first SSH connection fails, or grep does not manage to fish out the code page properly.
It may be possible to avoid the 2 SSH roundtrips by capturing the text and cutting the part after the chcp output.
Point (2) further above (the loss of Unicode characters which do not fit in the code page) is not covered in your solution. Maybe it is impossible to fix that issue.
Like explained above, ssh without -t does not work reliably, and ssh with -t requires further parsing and cleaning of the capture text.
I’ve looked into the internals of sshd, and it appears that implementing this on the sshd side is not feasible; a non-interactive session (ssh user@host <command>) is byte-oriented, whereas conversion requires buffering and reassembling UTF-8 fragments that may be split across packet boundaries. Consequently, some form of external translation is unavoidable. Perhaps the most logical approach would be to handle this on the Windows side by converting the resulting output (where the system code page is known locally).
Last time I had quotation issues with cmd.exe over SSH, I thought I should just upload a script to the Windows side the first time around, so that I could call it every time and send and receive the cmd commands from Linux as hex-encoded data blocks. That is, implement a kind of MIME encoding and/or chunking over SSH, where each command is packed and unpacked at the SSH boundary. A similar solution could also help with the character encoding problem.
The trouble is, I cannot really devote any more time to these Windows issues. Fortunately, my needs have been quite modest, so that custom hacks have sufficed for me.
If I remember well, I did not have these encoding issues in the past with Cygwin's SSH server. Alternatively, I wonder whether I could install a standard Linux SSH server in the Windows Subsystem for Linux (WSL), and be done with it.
Alternatively, I wonder whether I could install a standard Linux SSH server in the Windows Subsystem for Linux (WSL), and be done with it.
You might want to try switching the native Windows System Locale to UTF-8 globally (the equivalent of checking "Beta: Use Unicode UTF-8 for worldwide language support" in the desktop gui).
If anything goes wrong after the reboot, you can restore the original codepages by running:
reg import C:\codepage_backup.reg
Note: In my personal experience, changing the native Windows System Locale to UTF-8 (CP65001) can trigger unexpected BSODs at a completely random moment in time, creating the illusion that everything is working correctly with CP65001 and that the problem lies with the hardware. This happens likely due to legacy NT drivers or deep system components mismanaging memory when they unexpectedly encounter CP65001. Upon analyzing the memory dumps following these BSODs, I haven't been able to identify any common pattern other than the presence of CP65001 globally.
Details
For a long time, I experienced completely random, seemingly chaotic BSODs on a Windows machine where the global UTF-8 checkbox was active. On July 1st, I turned it off (reverting to native CP437). The machine immediately achieved nearly 4 months of flawless uptime and absolute stability. Today, to test the hypotheses discussed in this thread, I re-enabled global UTF-8. The system crashed into a BSOD during the very first user logout.
Here is the exact breakdown of the crash dumps captured from the same machine:
Crash Date
Global Code Page
BSOD Code
Process/Component
What Gemini Thinks Happened (analyzing memory dumps)
Jan 20, 2026
65001
KERNEL_SECURITY_CHECK_FAILURE (139)
StartMenuExperienceHost.exe: - igdkmd64.sys
Destruction of the heap index balanced tree (INVALID_BALANCED_TREE) triggered by variable-width UTF-8 metadata strings in the graphics stack.
Jan 29, 2026
65001
CRITICAL_PROCESS_DIED (ef)
csrss.exe: - ntdll!NtTerminateProcess
Heap corruption inside gdi32full!pwszAllocNtMultiplePath while parsing multi-path font configurations, killing the critical Win32 subsystem.
Feb 03, 2026
65001
SYSTEM_SERVICE_EXCEPTION (3b)
svchost.exe: - nt!PspReturnQuota
Pointer corruption inside the Configuration Manager (nt!CmpCloseKeyObject) when a service closed an ANSI-queried registry key.
Feb 22, 2026
65001
INVALID_IO_BOOST_STATE (13c)
System (Kernel worker thread): - nt!PspThreadDelete
Asynchronous I/O packets (IRP) mismatched due to text string size miscalculations, causing a thread to exit with an un-zeroed I/O boost state.
Mar 27, 2026
65001
SYSTEM_SERVICE_EXCEPTION (3b)
Telegram.exe: - win32kfull!xxxFreeWindow
Native sanity checks (Asserts) triggered a hard breakpoint (int 3) because window flags and structure boundaries were corrupted by multibyte text.
Apr 07, 2026
65001
CRITICAL_STRUCTURE_CORRUPTION (109)
csrss.exe: - Kernel PatchGuard
Session pool corruption inside NtUserSetWindowLongPtr. PatchGuard detected the modified generic session data region and halted the kernel.
Apr 26, 2026
65001
SYSTEM_SERVICE_EXCEPTION (3b)
svchost.exe: - win32kfull!IsClassAtomRegistered
Access Violation caused by memory overwrites in the global Win32 Atom cache table when a service attempted to unregister a localized window class.
Jul 01, 2026
65001
MEMORY_MANAGEMENT (1a)
svchost.exe: - nt!RtlpHpVsChunkSplit
Severe kernel panic. Multibyte text handling inside NtUserThunkedMenuItemInfo caused a pool overflow that corrupted the hardware Page Table Entries (PTE).
Jul 1 – Oct 26, 2026
437
NO CRASHES
Nearly 4 months of 100% stability. Zero software anomalies or hardware faults.
Oct 26, 2026
65001
SYSTEM_SERVICE_EXCEPTION (3b)
mDNSResponder.exe: - NETIO.SYS (Network I/O)
BSOD upon user logout. The string expansion corrupted the kernel LFH heap and invalidated a vital SpinLock object.
The trouble is, I tend to write client scripts that access Windows Servers which are not mine, so such global changes are often more expensive (in administrative terms) than doing custom hacks in my scripts. Besides, your warning about suspicious BSODs would let the IT guy's eyebrows raise even higher.
Globally changing the default code page is surely a solution in some situations. If you think that drivers may cause problems, it may not be an issue at all if the Windows Server runs in a virtual machine with standard virtual device drivers.
Looking at all this, we can draw the following conclusions:
Nothing can be done on the Win32-OpenSSH (sshd.exe) side.
The system code page must remain untouched.
chcp 65001 requires an allocated PTY and will not work without ssh -t (the windows console server, conhost.exe, is always allocated alongside the console process, regardless of whether a PTY allocation is requested; chcp modifies its state).
It is necessary to abandon the native Windows cli-utility stack entirely in favor of utilities that explicitly output in UTF-8 format regardless of the system code page (native utilities inevitably truncate the character set to the system code page).
I think I’ve found the root of all evil (and all the encoding-related issues on Windows):
Here’s the situation:
The Windows console server, conhost.exe, is always allocated alongside the console process, regardless of whether a PTY allocation is requested.
chcp modifies the console server's state simply by calling SetConsoleOutputCP(<codepage>), which sets the <codepage> for STDOUT.
The Windows console server translates only the output sent via STDOUT from the console process into the current <codepage>.
Bytes received from STDERR are passed through SSH in their raw, unmodified form.
Utilities like quser report errors to STDERR using either the system code page or UTF-16 encoding; as a result, the data arriving via SSH consists of either raw bytes in the global system encoding or UTF-16 bytes, which completely breaks the output.
Activity
github-actions commentedon Oct 11, 2026
This looks like a Windows-specific UTF-8/codepage handling issue for remote commands executed through
sshd, so I’m marking it for maintainer investigation.For maintainers
äto CP437 (0x84) and the server side is OpenSSH_for_Windows running PowerShell 5.1 on Windows build 10.0.20348, so the likely fix area is Windows process/console/codepage handling in PowerShell/openssh-portable. Confidence: medium-high.ssh MyWindowsPC "echo ä"), expected/actual output, and OpenSSH_for_Windows version (9.5p2). It does not name the Linux client distribution/version or the exact remote shell/codepage settings, but the core repro is specific enough for initial investigation. Confidence: medium.Area-sshdandArea-Terminalbecause the symptom occurs when a Linux SSH client invokes a command on the Windowssshdserver and receives escaped/non-UTF-8 output from the remote command path.Related issues checked
sshclient, not Linux-client-to-Windows-server remote command output.Add this agentic workflows to your repo
To install this agentic workflow, run
jborean93 commentedon Oct 12, 2026
It really depends on what the target default shell is and the configured console codepage. For example my target
DefautlShellispowershell.exeso I need to explicitly change the codepage withchcp 65001or[Console]::OutputEncoding = [Text.UTF8Encoding]::new()to tell PowerShell it can emit characters using UTF-8 bytes.Windows does not set the console output codepage to UTF-8 by default for compatibility issues so you essentially need to tell it that UTF-8 is ok.
If your default shell is cmd (the default if not configured) then doing
chcp 65001 && echo äshould work but I've not tested that.rdiez commentedon Oct 12, 2026
I think the default shell on my Windows Server target is cmd. I tested it like this:
This does not work:
However, this does work:
But that trick does not always work:
The
\201on the last line should actually be 'ü'.So it looks like the
chcp 65001is not been set or propagated properly in all cases.jborean93 commentedon Oct 12, 2026
I do see that when cmd is the default shell the chcp command didn't apply to the current process and I had to spawn a new one so it saw the new console codepage changes.
As for why
quserstill returns the\201escape sequence I am not sure sorry. It could be thatquserdoesn't respect the console code page and always returns it in some other encoding. You could rule this out by opening cmd interactively on the Windows host and run the samechcp 65001then thequser ...and redirect stdout/stderr to a file. From there you can inspect the raw bytes or useFormat-Hex -Path ...in PowerShell to see whether theüis UTF-8 encoded (quser respects the codepage) or something else (quser does not).Ultimately this is a tricky thing, SSH just returns back the raw bytes it receives on the stdout/stderr so it is up to the applications that write to stdout/stderr to emit using the expected encoding. If there is a misalignment then you get issues like this where your client cannot render what the remote had emitted.
rdiez commentedon Oct 24, 2026
I think
quserignores the console's code page when redirecting to a file. Allegedly, other such 'old' command-line programs behave that way.I am guessing that, when I connect from a Linux client, the SSH server (OpenSSH for Windows) creates a virtual console, but
quserstill feels it is not a real console, but some kind of redirection.Even if I start a nested
cmd(the trick explained above), it still does not work:Note the \201 again.
You can allegedly change the global default code page for such applications, but I do not really want to touch global settings on a server which I do not operate. Note that
quseris a tool for Remote Desktop, so it is not an ancient legacy program.I guess the only solution is to accept that
qusergenerates text in code page 850, at least when running on European systems, and convert the text as necessary.This is a general Windows problem, not really related to SSH. If a local Windows program wants to capture the text output from
quser, it will have the same problem.Therefore, unless somebody else has any other ideas, or maybe contradicting information (I am no expert here), I would close this bug report.
o-sdn-o commentedon Oct 25, 2026
You may need to explicitly specify the requirement for PTY allocation on the remote side:
https://man7.org/linux/man-pages/man1/ssh.1.html:
Side effect: #1738
rdiez commentedon Oct 25, 2026
Thanks for the trick. Option '-t' does work better, characters like 'ä' are properly encoded.
The trouble is the side effect you mentioned. The console output you posted above does not show that the terminal gets cleared, which gives you a clue that something extra is going on.
I am redirecting the output of the whole ssh command to a file, in order to process the text given back, and then you see the terminal control codes in that file, and even cmd's prompt. For example command:
ssh -t MyRemoteHost "echo ä && echo ä" >test.txtyields this content in test.txt:
Where
<ESC>is character code 0x1B and<BEL>is 0x07.The pseudo-terminal seems to still be in code page 850, or at least that is what chcp reports when using "ssh -t". That also means that all Unicode characters which cannot be represented in 850 will probably get lost.
It looks like there is no easy, right way. No matter what option you choose, you have to add workarounds and/or live with lost Unicode characters.
o-sdn-o commentedon Oct 25, 2026
Perhaps the best option is just to convert the incoming stream encoding:
rdiez commentedon Oct 25, 2026
Yes, I already mentioned that option further above. But this technique has its drawbacks: 1) On European servers the code page tends to be 850 instead of 437. As a client, you would have to find out first how the server is configured. 2) Unicode characters which do not fit the code page will get lost. 3) Some other command-line tools may actually honour the console code page, so this technique depends on the tool, and perhaps even on the tool version. It is all probably doable, but a big pain.
o-sdn-o commentedon Oct 25, 2026
There is a significant difference depending on where the conversion takes place:
iconvwith a detected/peeked code page): while the tools on the Windows side rely on the system code page, everything is ultimately converted to UTF-8 once it reaches the Linux environment.Test:
PS: It would certainly be cool to offload this logic to the sshd service on the Windows side.rdiez commentedon Oct 25, 2026
First of all, thanks for the example code.
I would add some error detection, in case the first SSH connection fails, or grep does not manage to fish out the code page properly.
It may be possible to avoid the 2 SSH roundtrips by capturing the text and cutting the part after the chcp output.
Point (2) further above (the loss of Unicode characters which do not fit in the code page) is not covered in your solution. Maybe it is impossible to fix that issue.
Like explained above, ssh without -t does not work reliably, and ssh with -t requires further parsing and cleaning of the capture text.
So it remains hacking at best.
o-sdn-o commentedon Oct 25, 2026
I’ve looked into the internals of
sshd, and it appears that implementing this on thesshdside is not feasible; a non-interactive session (ssh user@host <command>) is byte-oriented, whereas conversion requires buffering and reassembling UTF-8 fragments that may be split across packet boundaries. Consequently, some form of external translation is unavoidable. Perhaps the most logical approach would be to handle this on the Windows side by converting the resulting output (where the system code page is known locally).rdiez commentedon Oct 25, 2026
OK, thanks for your feedback.
Last time I had quotation issues with cmd.exe over SSH, I thought I should just upload a script to the Windows side the first time around, so that I could call it every time and send and receive the cmd commands from Linux as hex-encoded data blocks. That is, implement a kind of MIME encoding and/or chunking over SSH, where each command is packed and unpacked at the SSH boundary. A similar solution could also help with the character encoding problem.
The trouble is, I cannot really devote any more time to these Windows issues. Fortunately, my needs have been quite modest, so that custom hacks have sufficed for me.
If I remember well, I did not have these encoding issues in the past with Cygwin's SSH server. Alternatively, I wonder whether I could install a standard Linux SSH server in the Windows Subsystem for Linux (WSL), and be done with it.
o-sdn-o commentedon Oct 26, 2026
You might want to try switching the native Windows System Locale to UTF-8 globally (the equivalent of checking "Beta: Use Unicode UTF-8 for worldwide language support" in the desktop gui).
reg export HKLM\System\CurrentControlSet\Control\Nls\CodePage C:\codepage_backup.reg /yIf anything goes wrong after the reboot, you can restore the original codepages by running:
reg import C:\codepage_backup.regNote: In my personal experience, changing the native Windows System Locale to UTF-8 (CP65001) can trigger unexpected BSODs at a completely random moment in time, creating the illusion that everything is working correctly with CP65001 and that the problem lies with the hardware. This happens likely due to
legacyNT drivers or deep system components mismanaging memory when they unexpectedly encounter CP65001. Upon analyzing the memory dumps following these BSODs, I haven't been able to identify any common pattern other than the presence of CP65001 globally.Details
For a long time, I experienced completely random, seemingly chaotic BSODs on a Windows machine where the global UTF-8 checkbox was active. On July 1st, I turned it off (reverting to native
CP437). The machine immediately achieved nearly 4 months of flawless uptime and absolute stability. Today, to test the hypotheses discussed in this thread, I re-enabled global UTF-8. The system crashed into a BSOD during the very first user logout.Here is the exact breakdown of the crash dumps captured from the same machine:
(analyzing memory dumps)
KERNEL_SECURITY_CHECK_FAILURE (139)StartMenuExperienceHost.exe:-
igdkmd64.sysINVALID_BALANCED_TREE) triggered by variable-width UTF-8 metadata strings in the graphics stack.CRITICAL_PROCESS_DIED (ef)csrss.exe:-
ntdll!NtTerminateProcessgdi32full!pwszAllocNtMultiplePathwhile parsing multi-path font configurations, killing the critical Win32 subsystem.SYSTEM_SERVICE_EXCEPTION (3b)svchost.exe:-
nt!PspReturnQuotant!CmpCloseKeyObject) when a service closed an ANSI-queried registry key.INVALID_IO_BOOST_STATE (13c)System(Kernel worker thread):-
nt!PspThreadDeleteSYSTEM_SERVICE_EXCEPTION (3b)Telegram.exe:-
win32kfull!xxxFreeWindowint 3) because window flags and structure boundaries were corrupted by multibyte text.CRITICAL_STRUCTURE_CORRUPTION (109)csrss.exe:- Kernel PatchGuard
NtUserSetWindowLongPtr. PatchGuard detected the modified generic session data region and halted the kernel.SYSTEM_SERVICE_EXCEPTION (3b)svchost.exe:-
win32kfull!IsClassAtomRegisteredAccess Violationcaused by memory overwrites in the global Win32 Atom cache table when a service attempted to unregister a localized window class.MEMORY_MANAGEMENT (1a)svchost.exe:-
nt!RtlpHpVsChunkSplitNtUserThunkedMenuItemInfocaused a pool overflow that corrupted the hardware Page Table Entries (PTE).SYSTEM_SERVICE_EXCEPTION (3b)mDNSResponder.exe:-
NETIO.SYS(Network I/O)rdiez commentedon Oct 26, 2026
Thanks for the Registry information.
The trouble is, I tend to write client scripts that access Windows Servers which are not mine, so such global changes are often more expensive (in administrative terms) than doing custom hacks in my scripts. Besides, your warning about suspicious BSODs would let the IT guy's eyebrows raise even higher.
Globally changing the default code page is surely a solution in some situations. If you think that drivers may cause problems, it may not be an issue at all if the Windows Server runs in a virtual machine with standard virtual device drivers.
o-sdn-o commentedon Oct 26, 2026
Looking at all this, we can draw the following conclusions:
(the windows console server, conhost.exe, is always allocated alongside the console process, regardless of whether a PTY allocation is requested;chcp 65001requires an allocated PTY and will not work withoutssh -tchcpmodifies its state).Result:
o-sdn-o commentedon Oct 27, 2026
I think I’ve found the root of all evil (and all the encoding-related issues on Windows):
Here’s the situation:
conhost.exe, is always allocated alongside the console process, regardless of whether a PTY allocation is requested.chcpmodifies the console server's state simply by callingSetConsoleOutputCP(<codepage>), which sets the<codepage>for STDOUT.<codepage>.quserreport errors to STDERR using either the system code page or UTF-16 encoding; as a result, the data arriving via SSH consists of either raw bytes in the global system encoding or UTF-16 bytes, which completely breaks the output.