os.chmod

chmod sets the complete permission mode; umask decides which bits new files do NOT get. On Windows chmod only toggles the read-only flag (the write bits) and ignores everything else. chown, fchown and lchown are Unix only; lchmod exists on Windows (3.13+) and macOS/BSD but not on Linux.

os functionPython 3 (all) (fchmod/lchmod on Windows 3.13+)
Common call
os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
Returns
None (umask: the previous mask)
Replaces
subprocess.run(["chmod", "+x", path])
Watch out
Octal literals: 0o644, not 644
os.chmod(pathpath — The file. chmod and chown also accept an open file descriptor (on Windows since 3.13 for chmod).type: str | bytes | PathLike | int · required, modemode — chmod/fchmod/lchmod: permission bits, an octal literal (0o644) or stat.S_* flags OR-ed together.type: int · required, *, dir_fddir_fd — Resolve a relative path against this open directory (Unix).type: int · default: None=None, follow_symlinksfollow_symlinks — False acts on a symlink itself (lchmod / lchown). The docs note the default is False on Windows.type: bool · default: True=True)
→ None | int

Parameters

NameTypeRequiredDescription
pathstr | bytes | PathLike | intyesThe file. chmod and chown also accept an open file descriptor (on Windows since 3.13 for chmod).
modeintyeschmod/fchmod/lchmod: permission bits, an octal literal (0o644) or stat.S_* flags OR-ed together.
uidintyeschown family: numeric user id; -1 leaves it unchanged.
gidintyeschown family: numeric group id; -1 leaves it unchanged.
maskintyesumask: bits to remove from the mode of newly created files and directories.
dir_fdintno (None)Resolve a relative path against this open directory (Unix).
follow_symlinksboolno (True)False acts on a symlink itself (lchmod / lchown). The docs note the default is False on Windows.

Return value

None | int — chmod/chown and their variants return None; umask returns the previous mask as an int.

Common patterns

Make a script executable (Unix)
Add the execute bit for owner, group and others to the current mode.
import os
import stat
mode = os.stat('deploy.sh').st_mode
os.chmod('deploy.sh', mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
Clear read-only before deleting (works on Windows)
A read-only file cannot be deleted on Windows; give the write bit back first.
import os
import stat
os.chmod(path, stat.S_IWRITE)
os.remove(path)
Create files with a private mask
Set the umask around the code that creates files, and always restore it - it is process-wide.
import os
old = os.umask(0o077)
try:
    with open('token.txt', 'w') as f:
        f.write(token)
finally:
    os.umask(old)
Change owner by name (Unix)
shutil.chown accepts user and group names; os.chown wants numeric ids.
import shutil
shutil.chown('/srv/app/data', user='www-data', group='www-data')

Examples

1. Remove the write permission
import os import stat open('ro.txt', 'w').close() os.chmod('ro.txt', 0o444) writable = bool(os.stat('ro.txt').st_mode & stat.S_IWUSR) os.chmod('ro.txt', 0o644) writable
Returns
False
2. Writing a read-only file fails
import os open('ro.txt', 'w').close() os.chmod('ro.txt', 0o444) try: open('ro.txt', 'w') except OSError as e: result = (type(e).__name__, e.errno) finally: os.chmod('ro.txt', 0o644) result
Returns
('PermissionError', 13)
3. fchmod through a descriptor
import os import stat open('f.txt', 'w').close() fd = os.open('f.txt', os.O_RDWR) try: os.fchmod(fd, 0o444) ro = not os.stat('f.txt').st_mode & stat.S_IWUSR os.fchmod(fd, 0o644) finally: os.close(fd) ro
Returns
True
4. Read a mode as text
import stat (stat.filemode(stat.S_IFREG | 0o755), stat.filemode(stat.S_IFDIR | 0o700))
Returns
('-rwxr-xr-x', 'drwx------')
5. What a umask of 0o022 leaves
(oct(0o666 & ~0o022), oct(0o777 & ~0o022))
Returns
('0o644', '0o755')
6. umask returns the old mask
import os old = os.umask(0o077) try: pass finally: os.umask(old) type(old).__name__
Returns
'int'
7. Missing file
import os try: os.chmod('ghost.txt', 0o644) except OSError as e: result = type(e).__name__ result
Returns
'FileNotFoundError'

Pitfalls

1. Writing the mode in decimal
644 is decimal (0o1204), which sets write-for-owner, read-for-others and the sticky bit. Permission modes are octal.
644
import stat
stat.filemode(stat.S_IFREG | 644)
'--w----r-T'
0o644
import stat
stat.filemode(stat.S_IFREG | 0o644)
'-rw-r--r--'
2. Replacing the mode when you meant to add a bit
chmod sets the whole mode. Read st_mode and OR the new bit in to keep the others.
only S_IRUSR
import os
import stat
open('f.txt', 'w').close()
os.chmod('f.txt', stat.S_IRUSR)
writable = bool(os.stat('f.txt').st_mode & stat.S_IWUSR)
os.chmod('f.txt', 0o644)
writable
False
OR with st_mode
import os
import stat
open('f.txt', 'w').close()
os.chmod('f.txt', os.stat('f.txt').st_mode | stat.S_IRUSR)
bool(os.stat('f.txt').st_mode & stat.S_IWUSR)
True

When to use

Use it
  • Making scripts executable, keys private (0o600), files read-only
  • Setting a private umask around code that creates sensitive files
  • Changing ownership in install or deploy scripts running as root (Unix)
Reach for something else
  • Windows ACLs → not reachable through chmod (icacls or pywin32)
  • Owner by user name → shutil.chown
  • Only the mode at creation time → os.open(path, flags, mode) / os.mkdir(path, mode)

Notes

CPython impl
Modules/posixmodule.c. fchmod(fd, m) is chmod(fd, m), lchmod(p, m) is chmod(p, m, follow_symlinks=False), fchown and lchown likewise for chown
Availability
chmod, umask: Unix and Windows. fchmod: Unix, Windows (3.13+). lchmod: Unix, Windows (3.13+), not Linux, not OpenBSD (it is in os.__all__ on Windows 3.13 and missing on Linux). chown, fchown, lchown: Unix only
Windows chmod
Only the read-only flag is set or cleared (stat.S_IWRITE / S_IREAD); the mode then reads back as 0o444 or 0o666. Execute, group and other bits are ignored. fchmod needs a descriptor opened for writing there: on a read-only descriptor it raised PermissionError on Windows 3.13 but worked on Linux
Windows umask
Only the 0o600 bits of the mask are kept: os.umask(0o022) stores 0 and os.umask(0o777) stores 0o600 (verified on Windows CPython 3.13)
root
On Linux, root bypasses the permission bits, so a 0o444 file is still writable by root

FAQ

Permission bits are an OS feature: Windows only honours the read-only flag, and on Unix the result also depends on the owner and the umask. The examples only show changes that read back the same on Linux and Windows, and they restore what they change.