Skip to content

How to get UTF-8 to work properly from a Linux SSH client #2468

Open

Description

@rdiez

Prerequisites

  • Write a descriptive title.
  • Make sure you are able to repro it on the latest version
  • Search the existing issues.

Steps to reproduce

How do I get UTF-8 to work properly from a Linux SSH client?

$ ssh MyWindowsPC "echo ä"
\204

Octal 204 = decimal 132 = hexadecimal 84 is the code for 'ä' in Code Page 437.

UTF-8 works fine on my Linux box. Environment variable LANG is set to "en_US.UTF-8".

Expected behavior

$ ssh MyWindowsPC "echo ä"
ä

Actual behavior

$ ssh MyWindowsPC "echo ä"
\204

Error details

Environment data

Name                           Value                                                                                   
----                           -----                                                                                   
PSVersion                      5.1.20348.5386                                                                          
PSEdition                      Desktop                                                                                 
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                 
BuildVersion                   10.0.20348.5386                                                                         
CLRVersion                     4.0.30319.42000                                                                         
WSManStackVersion              3.0                                                                                     
PSRemotingProtocolVersion      2.3                                                                                     
SerializationVersion           1.1.0.1

Version

OpenSSH_for_Windows_9.5p2, LibreSSL 3.8.2

Visuals

No response

Activity

  1. github-actions commented on Oct 11, 2026

    @github-actions

    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

    Generated by Agentic Issue Triage for issue #2468 · 63.9 AIC · ⌖ 18.2 AIC · ⊞ 20.1K · ◷

    Add this agentic workflows to your repo

    To install this agentic workflow, run

    gh aw add githubnext/agentics/workflows/issue-triage.md@2f03fdaafb8c1ae62dfde7e0be762a822a201aeb
    
  2. jborean93 commented on Oct 12, 2026

    @jborean93

    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.

    Image

    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.

  3. rdiez commented on Oct 12, 2026

    @rdiez
    Author

    I think the default shell on my Windows Server target is cmd. I tested it like this:

    $ ssh MyWindowsPC ver
    Microsoft Windows [Version 10.0.20348.5386]
    

    This does not work:

    $ ssh MyWindowsPC "chcp 65001 && echo ä"
    Aktive Codepage: 65001.
    \204
    

    However, this does work:

    $ ssh MyWindowsPC "chcp 65001 && cmd /c \"echo ä\""
    Aktive Codepage: 65001.
    ä
    

    But that trick does not always work:

    $ ssh MyWindowsPC "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.
    

    The \201 on the last line should actually be 'ü'.

    So it looks like the chcp 65001 is not been set or propagated properly in all cases.

  4. jborean93 commented on Oct 12, 2026

    @jborean93

    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.

    Image

    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.

  5. rdiez commented on Oct 24, 2026

    @rdiez
    Author

    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.

  6. o-sdn-o commented on Oct 25, 2026

    @o-sdn-o

    How do I get UTF-8 to work properly from a Linux SSH client?

    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:

    • 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.
      
    $ ssh user@host echo ä
    �
    
    $ ssh -t user@host echo ä
    ä
    

    Side effect: #1738

  7. rdiez commented on Oct 25, 2026

    @rdiez
    Author

    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:

    <ESC>[2J<ESC>[m<ESC>[Hä
    <ESC>]0;C:\Windows\system32\conhost.exe<BEL><ESC>[?25hä
    

    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.

  8. o-sdn-o commented on Oct 25, 2026

    @o-sdn-o

    Perhaps the best option is just to convert the incoming stream encoding:

    sdn@ubuntu2604:~$ ssh test@meg "echo ä" | iconv -f CP437 -t UTF-8
    ä
    sdn@ubuntu2604:~$ 
    
  9. rdiez commented on Oct 25, 2026

    @rdiez
    Author

    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.

  10. o-sdn-o commented on Oct 25, 2026

    @o-sdn-o

    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.

    Test:

    WIN_CP=$(ssh test@meg "chcp" | grep -o '[0-9]\+')
    ssh test@meg "echo ä" | iconv -f "CP${WIN_CP}" -t UTF-8
    

    PS: It would certainly be cool to offload this logic to the sshd service on the Windows side.

  11. rdiez commented on Oct 25, 2026

    @rdiez
    Author

    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.

  12. o-sdn-o commented on Oct 25, 2026

    @o-sdn-o

    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).

  13. rdiez commented on Oct 25, 2026

    @rdiez
    Author

    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.

  14. o-sdn-o commented on Oct 26, 2026

    @o-sdn-o

    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).

    • Backup current values:
      reg export HKLM\System\CurrentControlSet\Control\Nls\CodePage C:\codepage_backup.reg /y
    • Change the ACP (ANSI), MACCP, and OEMCP to 65001 (UTF-8):
      reg add "HKLM\System\CurrentControlSet\Control\Nls\CodePage" /v ACP /t REG_SZ /d 65001 /f
      reg add "HKLM\System\CurrentControlSet\Control\Nls\CodePage" /v MACCP /t REG_SZ /d 65001 /f
      reg add "HKLM\System\CurrentControlSet\Control\Nls\CodePage" /v OEMCP /t REG_SZ /d 65001 /f
    • Reboot the system.

    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.
  15. rdiez commented on Oct 26, 2026

    @rdiez
    Author

    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.

  16. o-sdn-o commented on Oct 26, 2026

    @o-sdn-o

    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).

    Result:

    sdn@ubuntu2604:~$ ssh test@meg "pwsh -Command \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; echo 'Привет äöü'\""
    Привет äöü
    sdn@ubuntu2604:~$ 
  17. o-sdn-o commented on Oct 27, 2026

    @o-sdn-o

    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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions