unzip archive.zip extracts into the current directory. unzip archive.zip -d /path/to/target extracts somewhere specific, and unzip -l archive.zip lists the contents without extracting — which is the one worth running first, because plenty of archives have no top-level folder and will scatter forty files into whatever directory you happen to be in.
The basic command is not the hard part. What causes actual trouble is everything around it: unzip not being installed on a minimal image, an archive that unpacks its contents loose across your home directory, filenames from a Windows machine that arrive as mojibake, and files that carry a .zip extension without being zips.
Table of contents
- The commands worth knowing
- When unzip is not installed
- Filename encoding, the reliably annoying one
- Archives that are not what they claim
- Extracting untrusted archives safely
- How this fits the rest of the stack
- FAQ
The commands worth knowing
unzip archive.zip # into the current directory
unzip archive.zip -d /opt/app # into a specific directory
unzip -l archive.zip # list contents, extract nothing
unzip -o archive.zip # overwrite existing files without asking
unzip -n archive.zip # never overwrite
unzip -q archive.zip # quiet
Make -l a habit before extracting anything you did not create. It takes a second and it tells you whether the archive is well-behaved:
$ unzip -l release.zip
Length Date Time Name
--------- ---------- ----- ----
0 2026-08-20 14:22 myapp/
1420 2026-08-20 14:22 myapp/index.js
8891 2026-08-20 14:22 myapp/package.json
Every path beginning with myapp/ means one clean directory appears. Paths with no common prefix mean the contents land loose.
If they do, extract into a directory you make yourself and skip the problem entirely:
mkdir -p extracted && unzip messy.zip -d extracted
To pull out a single file rather than the whole archive:
unzip archive.zip "path/inside/file.txt" -d /tmp/
Quote the pattern. Unquoted, the shell may expand it against your local files before unzip ever sees it.
When unzip is not installed
Minimal server images and slim container bases routinely omit it, which is a surprising thing to discover mid-deploy.
# Debian / Ubuntu
sudo apt update && sudo apt install -y unzip
# RHEL / Fedora / Rocky
sudo dnf install -y unzip
# Alpine
apk add --no-cache unzip
If you cannot install packages — a locked-down container, a build step with no root — Python is usually present and can do it in one line:
python3 -m zipfile -e archive.zip target-directory/
That is a genuinely useful fallback in a Dockerfile where adding unzip would mean another layer and another package to keep patched.
bsdtar from libarchive also handles zip, and often ships where unzip does not:
bsdtar -xf archive.zip -C target-directory/
Filename encoding, the reliably annoying one
Zip predates any sane agreement on filename encoding. Archives created on Windows frequently store names in a legacy code page, and on a UTF-8 Linux system those come out as garbage:
├── докÑменÑ.txt
└── каÑÑинка.jpg
unzip has a flag for it, if your build has the support compiled in:
unzip -O cp936 archive.zip # Chinese
unzip -O cp1251 archive.zip # Cyrillic
unzip -O cp932 archive.zip # Japanese
Where that is unavailable, 7z handles encodings more reliably:
sudo apt install -y p7zip-full
7z x archive.zip -o./target
Note the unusual syntax — -o with no space before the path. It is easy to get wrong and the error is unhelpful.
A related trap: filenames that are legal on Windows and not on Linux, or that differ only by case. Extracting a zip containing both README.md and readme.md onto a case-insensitive filesystem silently gives you one file.
Archives that are not what they claim
A .zip extension is a naming convention, not a guarantee. Check what you actually have:
file archive.zip
# archive.zip: Zip archive data, at least v2.0 to extract
# archive.zip: gzip compressed data <- not a zip
# archive.zip: HTML document text <- a download that failed
That last one is common and wastes real time. A curl that hit a login page or a rate limit saves the HTML error page under your intended filename, and unzip reports a corrupt archive when the actual problem is that the download never happened.
Test integrity without extracting:
unzip -t archive.zip
For genuinely truncated archives, -F attempts a repair and sometimes recovers most of the contents:
zip -FF broken.zip --out fixed.zip
And for multi-part archives — archive.z01, archive.z02, archive.zip — you need every part present in the same directory, and you point unzip at the .zip file, not the numbered ones.
Extracting untrusted archives safely
If the archive came from a user upload or an external source, two attacks are worth knowing about.
Path traversal. Entries named ../../etc/cron.d/backdoor write outside your target directory. Modern unzip refuses these, but not every library does — and application code that extracts uploads with a language’s zip module frequently does not check.
Inspect before extracting:
unzip -l untrusted.zip | grep -E '\.\.|^/' && echo "REFUSE: suspicious paths"
Decompression bombs. A few kilobytes expanding to many gigabytes fills the disk. Check the declared uncompressed size first:
unzip -l untrusted.zip | tail -1
Compare that against your free space before committing. In an application, enforce both a size cap and an entry-count cap, and extract to a temporary directory on a filesystem where filling up cannot take the service down with it.
The general rule: never extract untrusted input into a path that matters, and never trust the archive to tell you the truth about what it contains.
How this fits the rest of the stack
Extracting an archive is usually a step inside something bigger — unpacking a build artefact, restoring a backup, handling an upload. That context is where the failure modes actually matter, because a scattered extraction or a partly-downloaded file becomes a broken deploy rather than a messy directory.
When the archive is a release artefact, the better answer is generally not to be moving zips around at all. Building from a repository makes the deployed artefact reproducible from a commit, with a build log recording what happened and a previous deploy to roll back to. RunxBuild does that for services and static sites, and where uploads genuinely need to be stored and processed, persistent storage attaches to the service. The RunxBuild hosting calculator shows the service, storage and bandwidth as separate figures.
Useful related references:
- Unzip a File in Linux: unzip for .zip, tar for Everything Else
- tar Command in Linux: Create, Extract, List, Compress Archives
- How to Rename a File in Linux: mv, rename, and git mv
- Services on RunxBuild
FAQ
How do I extract a zip file to a specific directory in Linux?
Use unzip archive.zip -d /path/to/target. The directory is created if it does not exist. Without -d, contents go into your current working directory, which is how loose archives end up scattered across a home directory.
What if unzip is not installed?
Install it with apt install unzip, dnf install unzip, or apk add unzip. If you cannot install packages, python3 -m zipfile -e archive.zip target/ works wherever Python 3 is present, and bsdtar -xf archive.zip works where libarchive is available.
Why are the filenames garbled after extracting?
The archive was created on a system using a legacy code page rather than UTF-8. Try unzip -O cp1251 or the appropriate code page for the source language, or use 7z x which handles encodings more reliably than most unzip builds.
How do I see what is inside a zip without extracting it?
unzip -l archive.zip lists every entry with its size and path. Run this before extracting anything you did not create — it tells you whether the contents are wrapped in a single directory or will land loose in your current one.
Is it safe to extract a zip file from an untrusted source?
Not without checks. Look for path traversal entries with unzip -l untrusted.zip | grep -E '\.\.|^/', and compare the declared uncompressed total against your free space to catch decompression bombs. Always extract to a disposable temporary directory rather than a path that matters.