What it grants
7 is 4+2+1, three times over. Every class — owner, group, everyone else — gets read, write and execute. On a regular file that means any account can rewrite the contents and run it. On a directory it means any account can create entries, rename entries and, crucially, delete entries it does not own, because removing a file writes to the directory and never touches the file.
That last property is the one that causes real damage, and it is silent. There is no error message when another user's process deletes your upload; the file is simply not there any more. Which is why the fix for a genuinely shared directory is not 777.
777 versus 1777
The leading digit changes the semantics without changing who may read or write. Here is
777 with each of the eight possible leading digits, and what ls -l prints for
each:
| Mode | ls -l | Bits set | What it changes on a directory |
|---|---|---|---|
| 0777 | rwxrwxrwx | none | nothing beyond the nine bits |
| 1777 | rwxrwxrwt | sticky | only an entry’s owner (or the directory’s owner) may remove or rename it |
| 2777 | rwxrwsrwx | setgid | new entries inherit the directory’s group instead of the creator’s |
| 3777 | rwxrwsrwt | setgid + sticky | entries inherit the directory’s group, and only their owners may delete them |
| 4777 | rwsrwxrwx | setuid | nothing — Linux ignores setuid on directories |
| 5777 | rwsrwxrwt | setuid + sticky | only an entry’s owner (or the directory’s owner) may remove or rename it |
| 6777 | rwsrwsrwx | setuid + setgid | new entries inherit the directory’s group instead of the creator’s |
| 7777 | rwsrwsrwt | setuid + setgid + sticky | entries inherit the directory’s group, and only their owners may delete them |
1777 is what /tmp uses, and the reason is quoted directly from
POSIX §4.5: with S_ISVTX set on a writable directory, a process may remove or
rename a file only if it is the owner of the file, the owner of the directory, or has
appropriate privileges. Anyone can still create files. Nobody can delete anyone else's.
That is the shared-drop-directory behaviour people are reaching for when they type 777,
and it costs one extra digit.
Source: POSIX.1-2024 (IEEE Std 1003.1-2024), XBD §4.5 Directory Protection — The Open Group’s own free HTML edition, read directly.
Which errors 777 actually causes
This is the part that turns "777 is bad practice" into something you can act on. Of the rules indexed on this site, these are the ones a mode of 777 trips — they were not failing before you escalated, and they are failing now:
| What you see | Where it comes from | Fires on | Fix |
|---|---|---|---|
Permissions 0644 for '/home/deploy/.ssh/id_ed25519' are too open. OpenSSH client (ssh, scp, sftp, git over ssh) · stderr, before the connection is attempted shown for mode 644 on ~/.ssh/id_ed25519 (a private key)
verbatim format string: | OpenSSH (portable) — authfile.c · sshkey_perm_ok() | (st.st_uid == getuid()) && (st.st_mode & 077) != 0 quoted verbatim from the source 4032 of the 4096 modes fire it. | chmod 600 /home/deploy/.ssh/id_ed25519 least-privilege mode 600 |
Bad owner or permissions on /home/deploy/.ssh/config OpenSSH client (ssh) · stderr; ssh exits immediately (fatal) shown for mode 664 on ~/.ssh/config verbatim format string: | OpenSSH (portable) — readconf.c · read_config_file_depth(), SSHCONF_CHECKPERM branch | ((sb.st_uid != 0 && sb.st_uid != getuid()) || (sb.st_mode & 022) != 0) quoted verbatim from the source 3072 of the 4096 modes fire it. | chmod 600 /home/deploy/.ssh/config least-privilege mode 600 |
Authentication refused: bad ownership or modes for file /home/deploy/.ssh/authorized_keys OpenSSH server (sshd) · the server’s log — /var/log/auth.log or journalctl -u sshd shown for mode 664 on ~/.ssh/authorized_keys on the server verbatim format string: | OpenSSH (portable) — misc.c · safe_path() OpenSSH (portable) — auth2-pubkeyfile.c · auth_openfile() safe_path() in misc.c writes this into an error buffer; auth_openfile() in auth2-pubkeyfile.c prints the buffer with logit("Authentication refused: %s", line). | (!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || (stp->st_mode & 022) != 0 quoted verbatim from the source 3072 of the 4096 modes fire it. | chmod 600 /home/deploy/.ssh/authorized_keys least-privilege mode 600 |
(deploy) BAD FILE MODE (/var/spool/cron/deploy) cron (cronie) · syslog / journalctl -u crond shown for mode 700 on the crontab file itself, e.g. /var/spool/cron/deploy verbatim format string: | cronie — src/database.c · check_open() cronie — src/misc.c · log_it() check_open() in database.c calls log_it(uname, pid, "BAD FILE MODE", tabname, 0); log_it() in misc.c formats it for syslog with the format above, and the two trailing conversions stay empty because the err argument is 0. | (statbuf.st_mode & 07533) != 0400 quoted verbatim from the source 4088 of the 4096 modes fire it. | chmod 600 /var/spool/cron/deploy least-privilege mode 600 |
/etc/sudoers.d/deploy is world writable sudo · stderr, and sudo refuses to run shown for mode 646 on /etc/sudoers or a file in /etc/sudoers.d/ verbatim format string: | sudo — plugins/sudoers/sudoers.c · open_sudoers() sudo — lib/util/secure_path.c · sudo_check_secure() sudo_check_secure() in lib/util/secure_path.c classifies the mode and returns SUDO_PATH_WORLD_WRITABLE; open_sudoers() in plugins/sudoers/sudoers.c turns that return value into the message above. | sb->st_mode & S_IWOTH → SUDO_PATH_WORLD_WRITABLE, in sudo_check_secure() quoted verbatim from the source 2048 of the 4096 modes fire it. | chmod 440 /etc/sudoers.d/deploy least-privilege mode 440 |
Authentication refused: bad ownership or modes for directory /home/deploy/.ssh OpenSSH server (sshd) · the server’s log — /var/log/auth.log or journalctl -u sshd shown for mode 775 on ~/.ssh, and every directory above it up to the home directory verbatim format string: | OpenSSH (portable) — misc.c · safe_path() OpenSSH (portable) — auth2-pubkeyfile.c · auth_openfile() Same two-file assembly as the file variant: safe_path() fills the buffer while walking upward from the key file, auth_openfile() prefixes "Authentication refused: ". | stat(buf, &st) == -1 || (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || (st.st_mode & 022) != 0 — inside the loop that walks each parent in turn quoted verbatim from the source 3072 of the 4096 modes fire it. | chmod 700 /home/deploy/.ssh least-privilege mode 700 |
sudoers-world-writable — Two mode conditions, not one: the next branch, ISSET(sb->st_mode, S_IWGRP) && (gid == (gid_t)-1 || sb->st_gid != gid), rejects a group-writable file too unless its group is the one sudo expects — so 664 fails here for a different reason than 666 does. Fix these with visudo, which validates before replacing the file.
Those rows are shown here for the one property this page is about — that 777 trips them. The per-row caveats, and the 6 rules 777 does not trip, are on the error index.
The sudo row deserves emphasis because the failure mode is so unhelpful: a world-writable
file in /etc/sudoers.d/ makes sudo refuse to run at all, which on a machine
where you reach root only through sudo means you have locked yourself out. Use
visudo -f for those files; it validates before replacing, and it sets the mode
for you.
What to use instead, per problem
- "The web server can't read my files." The server needs read on the file and search on every directory above it. That is 644 for files and 755 for directories — nine bits changed to five.
- "My application can't write to the upload directory." Give the
directory to the application's user with
chown, then 755. Ownership is the right lever here; widening the mode gives the same access to every other process on the box as well. - "Several people need to write to this directory." A shared group, mode
2775, and a umask of002. The setgid bit makes new entries inherit the group; the umask keeps the group-write bit from being masked off at creation. - "Anyone should be able to drop files here."
1777. The sticky bit is the entire point. - "My script won't run."
chmod +x, which sets three bits and no more. If you want it private, 700.
Undoing it
There is no "restore previous modes" — the old values are not recorded anywhere once chmod has run. What you can do is re-establish a sane baseline by type:
find /var/www/html -type d -exec chmod 755 {} +
find /var/www/html -type f -exec chmod 644 {} + Then walk back the exceptions by hand: scripts that need to be executable, keys that need to be 600, and anything owned by a service account. If the tree came from Git, a fresh clone restores the one mode bit Git tracks — it records only whether a file is executable, not the full mode — so a clone gives you correct execute bits and default everything else.
777 is not a stronger version of the mode you needed. It is a different mode that happens to include the bit you were missing, along with the others you were not — and at 777 that trips 6 of the rules indexed on this site, every one of them quoted above from the source that enforces it.