SQL Server on Linux for Windows admins: The habits that change
Your check script asks for free space with xp_fixeddrives. SQL Server on Linux answers: drive C, 22,327 MB free. There is no drive C on this server. The number is still correct; it belongs to the file system under /var/opt/mssql.
It is the same with many routines that are second nature on Windows. Some work unchanged, some return Windows answers to Linux questions, some lead nowhere without reporting an error. There is no Configuration Manager, and no maintenance plan wizard either. This page walks through the areas where Windows admins have to relearn, each with the comparison "On Windows · On Linux · Pitfall". What we say about Linux, we measured on SQL Server 2017, 2019, 2022 and 2025. What we say about Windows comes from Microsoft's documentation.
In 60 seconds
- Files live under
/var/opt/mssql, andC:\exists anyway. SQL Server translatesC:\to/. A path likeC:\var\opt\mssql\data\x.mdfworks,C:\SQLData\x.mdffails. SQL Server ignores case in file names on Linux too. - Everything belongs to the user
mssql. A backup target without write permission formssqlends with a Windows error message:Operating system error 5(Access is denied.). mssql-confreplaces the Configuration Manager,systemctlreplaces service management. In the container, environment variables beat the configuration file at every start. If you movemasterwithmssql-confwithout taking the files along, you start with an emptymaster.- Windows calls return Windows answers or nothing. Registry queries return NULL without T-SQL reporting an error,
sys.dm_server_registryis empty, andsys.dm_server_serviceshas different gaps in each version. - Maintenance plans do not exist against Linux instances. Ola Hallengren's maintenance solution ran in our lab on all four versions without errors; sp_Blitz did too, but misreads some Windows answers.
- High availability without a Windows failover cluster: Pacemaker or no cluster manager at all, endpoints with certificates.
1 · Where does what live?
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| Program | C:\Program Files\Microsoft SQL Server\… | /opt/mssql | One instance per host, named instances do not exist |
| Data and log of new databases | Paths from setup, often separate drives | both /var/opt/mssql/data | Data and log live together until you change it with mssql-conf |
| Default backup directory | Path from setup | /var/opt/mssql/data | Backups land next to the database files |
| Errorlog | …\MSSQL\Log\ERRORLOG | /var/opt/mssql/log/errorlog | Text; the Agent log sqlagent.out is UTF-16 |
| Configuration | Configuration Manager, registry, startup parameters | /var/opt/mssql/mssql.conf | The file only exists once you set something |
| Memory dumps | …\MSSQL\Log | /var/opt/mssql/log |
Microsoft does not support symbolic links for data and log directories. Custom directories for data, log and backups, on the other hand, are common; they need the right permissions (section 2).
C:\ exists anyway
On Linux, SQL Server runs on an abstraction layer that presents a Windows environment to it. This shows in paths. We tried on all four versions what happens with Windows paths:
| Statement | Result |
|---|---|
CREATE DATABASE with file C:\SQLData\x.mdf | Error 5133, operating system error 2: the directory does not exist |
CREATE DATABASE with file C:\var\opt\mssql\data\x.mdf | works: the file lives in /var/opt/mssql/data/, the catalog still shows the Windows path |
BACKUP … TO DISK = 'C:\var\opt\mssql\data\x.bak' | works, the backup history shows the Windows path |
BACKUP … TO DISK = 'C:\Temp\x.bak' | 2017 (runs as root in the container): creates /Temp/x.bak; from 2019: error 3201, operating system error 5 |
BACKUP … TO DISK = '\var\opt\mssql\data\x.bak' | Error 3038: invalid file name |
BACKUP … TO DISK = 'x.bak' (without a path) | lands in the default backup directory |
RESTORE … WITH MOVE … TO 'D:\MSSQL\Data\x.mdf' | Error 5133, operating system error 2 |
So C:\ stands for /, other drive letters have no equivalent. This helps when a script with a fixed C:\ path happens to point to an existing Linux path, and it confuses because the catalog and backup history afterwards show Windows paths for files that live on Linux. Query 3 at the end of the page finds such paths. Our recommendation: always write Linux paths in T-SQL. When restoring a Windows backup, all files belong on Linux paths via WITH MOVE; that is the topic of part 6.
Upper and lower case
Linux file systems distinguish Fall.mdf and fall.mdf. SQL Server does not, not even on Linux:
- If
Fall.mdfwas already in the directory, SQL Server rejected a new database withfall.mdf(error 5170, the file already exists). - A backup named
L8.BAKcould be read withRESTORE … FROM DISK = '…/l8.bak'.
Tools outside SQL Server, such as a cleanup script in the shell, do see the exact spelling. If you write file names consistently in lowercase, you spare yourself both surprises.
2 · Who may do what?
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| Service account | Service account or virtual account, rights via NTFS | User mssql without interactive sign-in; everything under /var/opt/mssql belongs to it | Microsoft does not support changing the owner of the SQL Server directories |
| Custom backup directory | NTFS permission for the service account | Owner or group mssql, write permission | Without permission: Operating system error 5(Access is denied.) |
| New files | inherit NTFS permissions | belong to mssql, mode 640 or 660 | Other users and tools need group permissions |
| Additional confinement | – | SELinux (Red Hat) with package mssql-server-selinux | Custom data paths need the matching SELinux type |
You set up a custom backup directory like this:
sudo install -d -o mssql -g mssql -m 770 /srv/sqlbackup
sudo /opt/mssql/bin/mssql-conf set filelocation.defaultbackupdir /srv/sqlbackup
sudo systemctl restart mssql-serverNote the error message when the permission is missing: SQL Server reports it in Windows wording, Operating system error 5(Access is denied.), not as a Linux message. Anyone searching Linux logs for "Permission denied" will not find it.
SELinux: Without the package mssql-server-selinux, SQL Server runs on Red Hat without SELinux confinement. With the package, it runs in its own domain, and custom data paths must get the type mssql_db_t (semanage fcontext -a -t mssql_db_t "/srv/sqldata(/.*)?", then restorecon -R -v /srv/sqldata). This comes from Microsoft's documentation; we could not verify it in the container. We found no statement from Microsoft on AppArmor on Ubuntu.
In the container, SQL Server runs from 2019 as the user with ID 10001. Who must own the data directory there is described in part 3.
3 · Service and configuration
| On Windows | On Linux | Pitfall | ||||
|---|---|---|---|---|---|---|
| Control the service | Services console, Configuration Manager | `systemctl start\ | stop\ | restart\ | status mssql-server or T-SQL SHUTDOWN` | There is no pause; restarting the Agent also restarts the engine |
| Settings | Configuration Manager | mssql-conf, writes /var/opt/mssql/mssql.conf | Almost every change needs a restart | |||
| Trace flags at startup | Startup parameter -T | mssql-conf traceflag 3226 on | ||||
| Port, TLS | Configuration Manager | mssql-conf set network.tcpport, network.tlscert, network.tlskey | Certificate files must belong to mssql | |||
| Memory limit | max server memory | additionally memory.memorylimitmb for the whole process, default 80% | Set both limits, they mean different things | |||
| SQL Server Agent | Service "SQL Server Agent" | mssql-conf set sqlagent.enabled true | Disabled after installation | |||
Location of master | Startup parameters -d, -l | mssql-conf set filelocation.masterdatafile, …masterlogfile | Without moved files, a new, empty master is created (see below) |
The SQL Server Configuration Manager cannot connect to a Linux instance. This is what the replacement looks like:
sudo /opt/mssql/bin/mssql-conf set sqlagent.enabled true
sudo /opt/mssql/bin/mssql-conf traceflag 3226 on
sudo /opt/mssql/bin/mssql-conf set memory.memorylimitmb 12288
sudo cat /var/opt/mssql/mssql.conf
sudo systemctl restart mssql-serverAfter every change, mssql-conf answers with SQL Server needs to be restarted in order to apply this setting. In the 2022 and 2025 images it additionally points to sp_reload_mssqlconf, which can apply some of the settings without a restart; we did not check that.
Moving master: Caution
The safe procedure: stop the service, move the files master.mdf and mastlog.ldf to the new location, check that mssql owns them, only then set mssql-conf and start.
In the container: Variables win
In the container, you configure SQL Server with environment variables such as MSSQL_AGENT_ENABLED or MSSQL_MEMORY_LIMIT_MB. According to Microsoft they apply at setup; afterwards you are supposed to use mssql-conf. We combined both and restarted the container:
| Variable | Setting in mssql.conf | after the restart (2017–2025) |
|---|---|---|
MSSQL_AGENT_ENABLED=false | sqlagent.enabled = true | Agent off |
MSSQL_AGENT_ENABLED=true | sqlagent.enabled = false | Agent on |
MSSQL_MEMORY_LIMIT_MB=2000 | memory.memorylimitmb = 3000 | 2,000 MB |
The variable won on all versions at every start. For the memory limit, this precedence is also in the documentation; for the Agent we only saw it in the lab. Therefore define a setting in the container in exactly one place: either as a variable in the container definition or in a mounted mssql.conf, not in both.
Two more observations from the container:
- In the SQL Server 2019 image,
mssql-confrefuses to run for the container user; it has to run as root. Once root has written the file, later calls as the container user fail withPermission denied to modify SQL Server configuration.and still end with exit code 0. A script that only checks the exit code considers the change done. systemctldoes not exist in the container. The container is restarted; how to shut it down cleanly in the process is shown in part 3.
Reading the errorlog
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| Engine errorlog | File ERRORLOG, sp_readerrorlog | File /var/opt/mssql/log/errorlog, sp_readerrorlog | The file is text, although the documentation says UTF-16 |
| Agent log | File SQLAGENT.OUT | File /var/opt/mssql/log/sqlagent.out | UTF-16 with byte order mark |
| Service events | Event Viewer | systemctl status mssql-server; in the container docker logs | Microsoft does not mention journalctl |
| Number of errorlogs kept | SSMS, registry | cannot be changed | see below |
In all images we checked, errorlog began with plain text, sqlagent.out with the bytes ff fe, the marker for UTF-16 little endian. So cat and grep work directly for the errorlog, and for the Agent log after a conversion:
sudo iconv -f UTF-16LE -t UTF-8 /var/opt/mssql/log/sqlagent.out | tail -20The number of retained errorlogs cannot be changed in SSMS against Linux, Microsoft says. The script SSMS runs for this on Windows writes a registry value. On Linux this call ran through without an error message in the lab and changed nothing.
4 · Storage, volumes, shares
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| File system | NTFS, ReFS | XFS or ext4, NFS from version 4.2 | Other file systems are not supported |
| Backup to a share | BACKUP … TO DISK = '\\server\share\…' | Mount the share as a directory (CIFS), then use the Linux path | UNC paths in T-SQL only via mssql-conf set uncmapping |
| Instant File Initialization | Right "Perform volume maintenance tasks" for the service account | effective for data files without a right | Some tools report it as disabled |
| Lock Pages in Memory | Right "Lock pages in memory" | no documented equivalent | All images we checked reported the memory model CONVENTIONAL |
Backups to a Windows share: On Linux, SQL Server does not address the share directly but writes to a directory under which the share is mounted. Microsoft shows this in the guides for failover cluster instances and log shipping; we found no dedicated guide for backups to shares. Following the pattern of the log shipping guide, with write permission:
sudo apt-get install cifs-utils # Red Hat: dnf install cifs-utils
sudo install -d -o mssql -g mssql -m 770 /var/opt/mssql/backup-share
# /etc/fstab, one line; credentials in a file readable only by root
//fileserver/sqlbackup /var/opt/mssql/backup-share cifs credentials=/root/.sqlbackup-creds,vers=3.0,uid=mssql,gid=mssql 0 0Afterwards, BACKUP DATABASE … TO DISK = '/var/opt/mssql/backup-share/…' writes to the share. Microsoft requires SMB 3.0 or newer. For signing in to the file server with Kerberos instead of a password file we found no guide from Microsoft, and we did not check the mount itself in the container.
Instant File Initialization means: SQL Server creates new data files without first filling them with zeros. On Windows the service account needs a right for this. On Linux this right does not exist, and we measured whether the shortcut works anyway:
| 2017 | 2019 | 2022 | 2025 | |
|---|---|---|---|---|
| New 4 GB data file | 0.7 s | 0.8 s | 0.8 s | 0.7 s |
| New 4 GB log file | 11.8 s | 11.6 s | 11.6 s | 11.5 s |
Data files are created without filling, log files are written with zeros as on Windows (trace flag 3004 showed this in the errorlog). The column instant_file_initialization_enabled in sys.dm_server_services shows this only on 2019 and 2025. On 2017 and 2022 the row for the engine is missing entirely, and tools that look there report IFI as disabled (section 7).
Lock Pages in Memory: For Linux we found neither an equivalent nor a statement from Microsoft. Microsoft's Linux recommendations cover Transparent Huge Pages and vm.swappiness instead; that is the topic of part 4.
5 · Sign-in and Active Directory
SQL logins work as on Windows. Windows logins against an Active Directory are possible but need setup that joining the domain takes care of on Windows:
- Join the Linux host to the domain (for example with
realmdand SSSD). - Create an AD user account for SQL Server, with AES encryption for Kerberos.
- Register the SPNs
MSSQLSvc/<FQDN>:<port>andMSSQLSvc/<hostname>:<port>for this account. - Create a keytab (
adutilon Linux orktpasson Windows), place it under/var/opt/mssql/secrets, ownermssql, mode 400. mssql-conf set network.privilegedadaccount <account>andnetwork.kerberoskeytabfile <path>, restart.- Logins as usual with
CREATE LOGIN [DOMAIN\user] FROM WINDOWS.
Pitfalls according to Microsoft: Clients must use the fully qualified host name (FQDN), not localhost or the IP address. If the password of the service account changes, SQL Server needs a new keytab. Domain-local groups are not supported. We did not set this up in the lab.
Every Linux instance brings two Windows logins: BUILTIN\Administrators and NT AUTHORITY\NETWORK SERVICE, both sysadmin. The steps of Agent jobs run under the second; the job history shows Executed as user: NT AUTHORITY\NETWORK SERVICE. According to Microsoft you should not delete BUILTIN\Administrators. Security checks that list Windows logins find both.
6 · Availability groups without a Windows failover cluster
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| Cluster | Windows failover cluster (WSFC) | Pacemaker (CLUSTER_TYPE = EXTERNAL) or none (NONE) | Without Pacemaker only manual failover |
| Endpoints | Kerberos or certificates | Certificates | Windows authentication for endpoints does not exist |
| Listener | Network name in AD | IP address as a cluster resource plus DNS entry | Short outage during failover |
| Distributed transactions | MSDTC | not in availability groups |
An availability group on Linux does not need an Active Directory. We built an availability group with CLUSTER_TYPE = NONE and certificate endpoints between Linux containers and ran eight manual failovers without data loss; the measurements are in our series FCI vs. Availability Groups. Part 5 of this series covers Pacemaker, fencing and automatic failover.
7 · Tools and scripts
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| SSMS | full scope | connects as usual | No maintenance plans, no Data Collector, number of errorlogs cannot be changed |
| Configuration Manager | yes | does not connect | mssql-conf (section 3) |
| Azure Data Studio | retired since Feb 28, 2026 | likewise | Successor: VS Code with the MSSQL extension; Agent and classic administration stay in SSMS |
sqlcmd, bcp | yes | yes, native | |
| dbatools | full scope | Commands that use only T-SQL | Commands using WMI or remoting target Windows (our interpretation from the source code) |
What your scripts get back
Many check and maintenance scripts ask Windows: drives, registry, services. We ran the common calls on all four versions as sa:
sa on SQL Server 2017 to 2025 in containers. Details are in the table.| Call | What it returns on Linux |
|---|---|
xp_fixeddrives | one drive C with the free space of the file system under /var/opt/mssql |
xp_dirtree 'C:\' | a mix of Windows folders (binn, Windows, Program Files) and Linux entries (.dockerenv, run) |
xp_dirtree '/var/opt/mssql' | the directory contents, as expected |
xp_regread (service account) | NULL with the message RegQueryValueEx() returned error 2; only 2019 returned "LocalSystem" |
xp_instance_regread (BackupDirectory) | /var/opt/mssql/data |
xp_instance_regread (other values) | mostly NULL, without a T-SQL error |
xp_instance_regwrite | runs without an error, changes nothing |
sys.dm_server_registry | no rows |
sys.dm_server_services | 2017 and 2022: only the Agent; 2019: engine as "Stopped" although it is running; 2025: engine and Agent. According to the DMV the Agent runs as "LocalSystem" |
sys.dm_os_volume_stats | Size and free space yes; mount point, volume name and file system type NULL |
xp_msver | Platform "NT x64" |
xp_readerrorlog, sp_readerrorlog | work |
sys.dm_os_host_info | Linux and the distribution; the reliable way to detect the platform |
The most dangerous row is the registry: a script that checks @@ERROR does not notice that it read nothing, and writes have no effect. Without sysadmin permissions, xp_fixeddrives, xp_dirtree and sys.dm_os_volume_stats returned empty results instead of errors. Our recommendation: scripts that are meant to run on both platforms should query sys.dm_os_host_info first and branch afterwards, as the two following projects do.
Maintenance without a maintenance plan: Ola Hallengren
The common replacement for maintenance plans is the SQL Server Maintenance Solution by Ola Hallengren. It detects Linux via sys.dm_os_host_info and adapts path separators and procedures. We installed and ran the version of Sep 12, 2026 with default settings on all four versions:
- Installation without an error message. On Linux it creates ten Agent jobs, all with T-SQL steps. It does not create the job "Output File Cleanup", which on Windows deletes old output files with PowerShell; on Linux the installer picks only jobs with T-SQL steps. The Agent cannot run operating system commands or PowerShell on Linux anyway.
- Backups, integrity checks, index maintenance ran without errors. Without
@Directory, it backed up to the default backup directory under/var/opt/mssql/data/<server>/<database>/FULL/; SQL Server created the subdirectories itself duringBACKUP. It deleted old backups with@CleanupTime. - The jobs ran successfully on 2022 and 2025 and wrote their output files to
/var/opt/mssql/log. On 2017 and 2019 we could not start them through the Agent (see Method and limits).
What you should add yourself on Linux: nobody cleans up the output files of the jobs, and without @Directory backups sit next to the database files. Specify your own backup directory and delete old output files with a cron job or a T-SQL job step.
Diagnostics: sp_Blitz
sp_Blitz from the First Responder Kit supports Linux fully according to the project page, except for sp_DatabaseRestore. With version 8.34 of Jul 2, 2026 it ran without an error message on all four versions, as did sp_BlitzFirst. Some findings on Linux rest on the Windows answers from the table above:
| Finding (original) | Meaning on Linux |
|---|---|
Drive C Space | the free space under /var/opt/mssql, not of a drive C |
Power Plan: "unknown power mode" | cannot be determined on Linux; part 4 covers the CPU settings |
Instant File Initialization Not Enabled (2017, 2022) | does not apply, IFI works (section 4); the engine is missing from sys.dm_server_services |
Invalid login defined with Windows Authentication: BUILTIN\Administrators | login from the installation, not an error in your configuration |
Sysadmins: NT AUTHORITY\NETWORK SERVICE | likewise; Agent jobs run under this login |
sp_Blitz detects the operating system and the container correctly. Read the other findings as usual, but check the rows on drives, power plan, IFI and Windows logins against this table before you act.
8 · Updates
| On Windows | On Linux | Pitfall | |
|---|---|---|---|
| Cumulative updates | Installer of the CU | Package manager from the repository mssql-server-<version> (dnf/yum, apt-get, zypper) | The repository fixes the major version |
| Without internet access | Copy the CU file | Download the package file and install it locally | |
| Back to an older version | Uninstall the CU | Downgrade the package, only within the major version | |
| In the container | – | new image with a new tag, same data volume | Part 3 |
sudo apt-get update && sudo apt-get install mssql-server # Ubuntu
sudo dnf update mssql-server # Red HatProfile: What does this instance have?
The following query summarizes the points on this page for a concrete instance: platform and paths, memory model, database files per directory with free space, and Windows leftovers in msdb, such as job steps with drive letters after a migration. Query 2 in part 3 covers the container limits and the Agent; Query 1 in part 1 shows which features missing on Linux an instance uses.
Query: Instance profile
/* PSG Stack · SQL Server on Linux and Docker · Query 3 "Instance profile" For Windows admins: where do data files and backups live, how much space is free there, which memory model applies, and which Windows leftovers are still in msdb? !!! WARNING — please read before running !!! - Read-only. The query changes no data and no setting. - Permissions: VIEW SERVER STATE and VIEW ANY DEFINITION for parts 1 and 2, read access to msdb for part 3. If a permission is missing, the part returns no rows or says so in the column note instead of showing false zeros. - MAXDOP 1: every part runs serially and uses no additional cores. - SQL Server 2017 and later. Also runs on Windows; the notes are written for Linux. Tested in the PSG lab on 2017, 2019, 2022 and 2025 in Linux containers, as sa and without sysadmin (12 Nov 2026); not tested on Windows. - Part 3 scans job steps and the backup history of the last 30 days. With a very large history in msdb this can take a few seconds.*/SET NOCOUNT ON; DECLARE @sql nvarchar(max);DECLARE @linux bit = 0;IF OBJECT_ID(N'sys.dm_os_host_info') IS NOT NULL SELECT @linux = CASE WHEN host_platform = N'Linux' THEN 1 ELSE 0 END FROM sys.dm_os_host_info; /* 1 · Platform, default paths, memory model */SET @sql = N'SELECT ' + CASE WHEN OBJECT_ID(N'sys.dm_os_host_info') IS NULL THEN N'N''n/a'' AS platform, N''n/a'' AS distribution, N''n/a'' AS release' ELSE N'h.host_platform AS platform, h.host_distribution AS distribution, h.host_release AS release' END + N', CONVERT(nvarchar(20), SERVERPROPERTY(''ProductVersion'')) AS version, CONVERT(nvarchar(260), SERVERPROPERTY(''InstanceDefaultDataPath'')) AS default_data_path, CONVERT(nvarchar(260), SERVERPROPERTY(''InstanceDefaultLogPath'')) AS default_log_path, ISNULL(CONVERT(nvarchar(260), SERVERPROPERTY(''InstanceDefaultBackupPath'')), N''n/a (before 2019)'') AS default_backup_path, CONVERT(nvarchar(260), SERVERPROPERTY(''ErrorLogFileName'')) AS errorlog, i.sql_memory_model_desc AS memory_model, CASE i.sql_memory_model_desc WHEN N''CONVENTIONAL'' THEN N''no locked pages (Lock Pages in Memory not active)'' WHEN N''LOCK_PAGES'' THEN N''locked pages active'' WHEN N''LARGE_PAGES'' THEN N''large pages active'' END AS memory_model_meaningFROM sys.dm_os_sys_info AS i' + CASE WHEN OBJECT_ID(N'sys.dm_os_host_info') IS NULL THEN N'' ELSE N' CROSS JOIN sys.dm_os_host_info AS h' END + N' OPTION (MAXDOP 1);';EXEC sys.sp_executesql @sql; /* 2 · Database files per directory with free space On Linux mount_point stays empty; size and free space are still reported. A path with C:\ is possible on Linux: SQL Server then places the file under / (C:\var\opt\mssql\data\x.mdf lives in /var/opt/mssql/data/x.mdf) and stores the Windows path. */IF HAS_PERMS_BY_NAME(NULL, NULL, N'VIEW ANY DEFINITION') = 1 SELECT LEFT(f.physical_name, LEN(f.physical_name) - pos.p) AS directory, f.type_desc AS file_type, COUNT(*) AS files, COUNT(DISTINCT f.database_id) AS databases, CONVERT(decimal(12, 1), SUM(CONVERT(bigint, f.size)) * 8 / 1048576.0) AS size_gb, CONVERT(decimal(12, 1), MAX(v.total_bytes) / 1073741824.0) AS volume_gb, CONVERT(decimal(12, 1), MIN(v.available_bytes) / 1073741824.0) AS free_gb, MAX(ISNULL(v.volume_mount_point, N'(empty)')) AS mount_point, w.note FROM sys.master_files AS f CROSS APPLY (SELECT CHARINDEX(N'/', REVERSE(f.physical_name)) AS s, CHARINDEX(N'\', REVERSE(f.physical_name)) AS b) AS r CROSS APPLY (SELECT CASE WHEN r.s = 0 THEN r.b WHEN r.b = 0 THEN r.s WHEN r.s < r.b THEN r.s ELSE r.b END AS p) AS pos CROSS APPLY (SELECT CASE WHEN @linux = 1 AND f.physical_name LIKE N'[A-Za-z]:\%' THEN N'Windows path on Linux: C:\ stands for /' ELSE N'' END AS note) AS w CROSS APPLY sys.dm_os_volume_stats(f.database_id, f.file_id) AS v GROUP BY LEFT(f.physical_name, LEN(f.physical_name) - pos.p), f.type_desc, w.note ORDER BY directory, file_type OPTION (MAXDOP 1);ELSE SELECT N'Database files per directory' AS directory, N'not checked: VIEW ANY DEFINITION is missing (sys.master_files would show no rows)' AS note; /* 3 · Windows leftovers in msdb (typical after a migration) Job steps with a drive letter or UNC path in the command or output file, job steps of type CmdExec, PowerShell or SSIS, backups of the last 30 days to Windows paths. */IF HAS_PERMS_BY_NAME(N'msdb.dbo.sysjobsteps', N'OBJECT', N'SELECT') = 1 AND HAS_PERMS_BY_NAME(N'msdb.dbo.backupmediafamily', N'OBJECT', N'SELECT') = 1BEGIN SELECT finding, count, example, N'' AS note FROM ( SELECT N'Job step with Windows path' AS finding, COUNT(*) AS count, MIN(j.name + N' / ' + s.step_name) AS example FROM msdb.dbo.sysjobsteps AS s JOIN msdb.dbo.sysjobs AS j ON j.job_id = s.job_id WHERE s.command LIKE N'%[A-Za-z]:\%' OR s.command LIKE N'%\\%' OR s.output_file_name LIKE N'[A-Za-z]:\%' OR s.output_file_name LIKE N'\\%' UNION ALL SELECT N'Job step CmdExec, PowerShell or SSIS', COUNT(*), MIN(j.name + N' / ' + s.step_name + N' (' + s.subsystem + N')') FROM msdb.dbo.sysjobsteps AS s JOIN msdb.dbo.sysjobs AS j ON j.job_id = s.job_id WHERE s.subsystem IN (N'CmdExec', N'PowerShell', N'SSIS') UNION ALL SELECT N'Backup of the last 30 days to a Windows path', COUNT(*), MIN(m.physical_device_name) FROM msdb.dbo.backupmediafamily AS m JOIN msdb.dbo.backupset AS b ON b.media_set_id = m.media_set_id WHERE b.backup_finish_date >= DATEADD(DAY, -30, GETDATE()) AND (m.physical_device_name LIKE N'[A-Za-z]:\%' OR m.physical_device_name LIKE N'\\%') ) AS x OPTION (MAXDOP 1);ENDELSE SELECT N'Windows leftovers in msdb' AS finding, NULL AS count, NULL AS example, N'not checked: read access to msdb (sysjobsteps, backupmediafamily) is missing' AS note;Sample output
From the lab: SQL Server 2022 CU27 in the container, as sa, after we created a database and a backup with a C:\ path for section 1:
platform distribution release version default_data_path default_backup_path memory_model
-------- ------------ ------- ----------- -------------------- ------------------- ------------
Linux Ubuntu 22.04 16.0.4295.3 /var/opt/mssql/data/ /var/opt/mssql/data CONVENTIONAL
directory file_type files databases size_gb volume_gb free_gb note
--------------------- --------- ----- --------- ------- --------- ------- ---------------------------------------
/var/opt/mssql/data LOG 7 7 .1 46.9 21.7
/var/opt/mssql/data ROWS 12 7 .2 46.9 21.7
C:\var\opt\mssql\data LOG 1 1 .0 46.9 21.7 Windows path on Linux: C:\ stands for /
C:\var\opt\mssql\data ROWS 1 1 .0 46.9 21.7 Windows path on Linux: C:\ stands for /
finding count example
-------------------------------------------- ----- ------------------------------------
Job step with Windows path 0 NULL
Job step CmdExec, PowerShell or SSIS 0 NULL
Backup of the last 30 days to a Windows path 1 C:\var\opt\mssql\data\l8_cmaster.bakColumns shortened. Here is how to read it: Data, log and backups all live under /var/opt/mssql/data, on a file system with 21.7 GB free; that is the default and rarely what you want in production (section 1). Two database files and one backup carry Windows paths although they sit in the same Linux directory. After a migration, you will mainly find job steps and backup targets here that still point to drives or shares on Windows. Without VIEW ANY DEFINITION, part 2 reports "not checked", and without read access to msdb, so does part 3.
Method and limits
Measured on Nov 12, 2026 in Linux containers on an Ubuntu VM (cgroup v2, Docker 29.8) with the Microsoft images SQL Server 2017 CU31, 2019 CU32, 2022 CU27 and 2025 CU10 (Ubuntu variants), Developer Edition; a fresh container without network for each version, Instant File Initialization with 4 GB files on the container file system (overlayfs). We ran Ola Hallengren's Maintenance Solution as of Sep 12, 2026 and the First Responder Kit 8.34 unchanged with default settings. What we did not measure ourselves comes from Microsoft Learn (page versions July 2025 to October 2026, read on Nov 12, 2026) and from the source code of the projects named. Limits:
- Container instead of server: No systemd, no SELinux, no domain, no mounted share. We describe
systemctl, SELinux, Active Directory, CIFS and Pacemaker according to the documentation, not our own measurement. - Agent on 2017 and 2019: The Agent of these versions signs in over the network to
(local),1433. In the container without a network this failed, and it exited. We therefore did not check Ola jobs on these versions through the Agent, only the procedures directly. This is a limit of our setup, not behavior on Linux. - No Windows comparison: The "On Windows" column rests on the documentation, not on our own measurements.
- dbatools: Statements from the source code, not tested.
- One run per case; times show orders of magnitude.
Further reading
- Microsoft Learn: "Configure SQL Server settings on Linux (mssql-conf)", "Start, stop, and restart SQL Server services on Linux", "SQL Server on Linux: Known issues", "SQL Server on Linux frequently asked questions", "Get started with SQL Server on SELinux", "Tutorial: Use adutil to configure Active Directory authentication", "Configure log shipping on Linux".
- Microsoft Learn: "What's happening with Azure Data Studio".
- Ola Hallengren: SQL Server Maintenance Solution. Brent Ozar Unlimited: First Responder Kit.
- In this series: Overview, Part 1: Linux, containers or Windows?, Part 3: What goes wrong in the container.
- At PSG: FCI vs. Availability Groups, Backup and restore.
Taking over a Linux instance?
Three things first: run Query 3 and look at where data, log and backups live and whether msdb still contains Windows paths. Check whether the Agent is enabled and whether any maintenance exists, because there cannot be a maintenance plan. And read /var/opt/mssql/mssql.conf and, in the container, the environment variables before you change anything with mssql-conf.