How to List All Files Installed by an RPM Package

You can list all files installed by an RPM package using the `rpm -ql` command if the package is already installed, or `rpm -qpl` if you only have the package file.

The scenario

You are troubleshooting a broken configuration or hunting for a rogue script that keeps overwriting your custom settings. You know the file lives somewhere under /etc or /usr/lib, but you have no idea which package dropped it there. You run a quick search, find the path, and now you need to know exactly what else that package installed. Maybe you are planning to remove it safely. Maybe you are auditing a system after a security incident. You need a complete, accurate inventory of every file the package owns.

What the package manager actually tracks

Fedora does not scatter files randomly across the disk. Every package ships with a manifest. The RPM database stores a strict ledger of which package claims ownership of every file it installs. Think of it like a library catalog. The catalog does not store the books themselves. It stores the exact shelf location, the title, and the call number. When you ask the system what a package contains, you are querying that catalog, not scanning the filesystem. This makes the lookup instant and reliable. It also means the database only knows about files the package explicitly declared during build time.

The database lives in /var/lib/rpm. It is a Berkeley DB structure that maps package names to file paths, permissions, checksums, and capabilities. When you install a package, dnf unpacks the archive and updates the database in a single transaction. When you remove a package, dnf deletes the files and removes the database entries. The filesystem and the database stay synchronized as long as you let the package manager do the work. Manual file edits in /etc drift from the package manifest. The database still claims ownership. That is why you must query the database for the original layout and check the filesystem for the current state. Config files in /etc are user-modified. Files in /usr/lib/ ship with the package. Edit /etc. Never edit /usr/lib/.

Always check the database first. The filesystem tells you what is there. The database tells you what belongs there.

Querying installed packages

When the package is already on your system, the RPM database is the fastest source of truth. The rpm command reads the local database directly. It bypasses the network and ignores repository metadata. This is the standard approach for day-to-day administration.

Here is how to list every file owned by an installed package.

rpm -ql firefox
# -q queries the local RPM database
# -l lists the absolute paths of every owned file
# Output scrolls quickly. Pipe to less if you need to search it.

The output contains absolute paths. It includes binaries, shared libraries, man pages, and configuration templates. It does not include files created dynamically at runtime. If you need to filter the results, chain it with grep. The database query itself is instantaneous, so filtering on the client side costs nothing. Large packages like kernel or glibc output thousands of lines. Pipe the output to wc -l to get a quick count before you start reading.

Run rpm -ql before you guess. The database knows exactly what shipped with the package.

Querying uninstalled RPM files

You sometimes download a .rpm file to test it in a staging environment or to audit an update before applying it to production. The package is not in the database yet. You cannot use the standard query flags. You need to inspect the archive itself.

Here is how to read the manifest from a raw RPM file.

rpm -qpl /tmp/firefox-120.0-1.fc40.x86_64.rpm
# -q queries the package
# -p tells rpm to read a file path instead of the database
# -l lists the files inside that specific archive
# The path must be absolute or correctly relative to your working directory

This command extracts the file list from the package header without touching your filesystem. It is safe to run on production servers. You can verify the exact configuration files a new version will drop before you commit to the installation. Always verify the path exists before running this. A typo in the filename returns a silent failure or a generic error. The command reads the RPM signature and header metadata to validate the archive structure. It does not verify GPG signatures by default. Add --checksig if you need cryptographic verification before trusting the file list.

Verify the archive path before you run it. A missing file breaks the command instantly.

Searching across repositories

The local RPM database only knows about what is currently installed. It cannot see packages sitting in enabled repositories. If you need to check what a package contains before installing it, or if you are troubleshooting a dependency conflict across multiple repos, you need dnf.

Here is how to query repository metadata for a package file list.

dnf repoquery -l --whatprovides firefox
# dnf repoquery talks to repository metadata instead of the local DB
# -l requests the file list
# --whatprovides resolves the package name across all enabled repos
# This pulls data from cache. Run dnf makecache first if the cache is stale

dnf repoquery is the modern replacement for older yum plugins. It handles modular streams, disabled repositories, and complex dependency graphs automatically. It is slower than rpm because it parses XML metadata, but it gives you a complete picture of what is available in your configured repos. Keep your metadata fresh. Stale cache returns outdated file lists. Run dnf upgrade --refresh weekly to keep the repository metadata synchronized with the upstream mirrors. The --refresh flag forces a metadata download without changing installed packages.

Refresh the cache before querying repos. Stale metadata lies.

Finding the owner of a known file

You often start with a file path instead of a package name. You find a suspicious binary in /usr/local/bin or a modified configuration in /etc/nginx. You need to know which package manages it so you can update or remove it safely.

Here is how to reverse-lookup the owning package for a specific path.

rpm -qf /usr/bin/firefox
# -q queries the database
# -f tells rpm to search by file path instead of package name
# Returns the exact package name and version that owns the path
# Fails with "not owned by any package" if the file is manual or generated

This command is essential for troubleshooting. It tells you immediately whether a file is managed by the system or dropped there by a third-party script. If the command returns not owned by any package, the file is either manually created, generated by a post-install script, or installed outside the package manager. Do not force-remove unowned files. They might be critical runtime assets. Check the directory structure and documentation before deleting anything that rpm -qf does not recognize.

Run rpm -qf on unknown files first. Guessing the package name wastes time.

Verifying SELinux contexts and permissions

Listing files is only half the job. You need to confirm the files actually exist on disk and match the package manifest. Drift happens. Manual edits, broken upgrades, or failed post-install scripts can leave the filesystem out of sync with the database.

Here is how to cross-reference the package manifest with the actual filesystem.

rpm -ql httpd | xargs ls -lZ
# rpm outputs the expected paths
# xargs passes each path to ls
# -l shows permissions and ownership
# -Z displays the SELinux security context
# Missing files show a No such file or directory error

The -Z flag is essential on Fedora. SELinux contexts dictate how processes interact with files. A file can exist on disk but still be inaccessible if the context is wrong. The output shows the current context. Compare it against the expected context from the package. If they diverge, run restorecon to fix it. Do not disable SELinux to work around a missing context. Fix the label instead. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before you assume the package is broken. The restorecon -Rv command recursively restores contexts based on the policy database. It is safe to run on /etc and /var directories.

Restore contexts before you debug access errors. Half the time the file is fine but the label is wrong.

Common pitfalls and missing files

You will occasionally see files on your system that rpm -ql does not list. This is not a bug. It is how RPM handles dynamic content. Post-install scripts often generate configuration files, create log directories, or download runtime assets. These files are created by the %post scriptlet, not declared in the package manifest. The RPM database does not track them. You will also see files created by systemd generators or udev rules. They live on disk but belong to no package.

Another common trap is the --whatprovides flag. It resolves capabilities, not just package names. If you query a capability that multiple packages provide, dnf repoquery returns a combined file list. This looks like one package owns everything. It does not. Filter the output by package name first. Configuration files in /etc are another edge case. RPM ships them as .rpmnew or .rpmsave when you have modified the original. The database still lists the original path. The filesystem holds your modified version. The package manager considers the file owned by the package, even though the content differs. Use rpm -V to verify integrity and spot content drift.

Run rpm -V before you assume a package is broken. Half the time the content just drifted.

Which command to run

Use rpm -ql when the package is already installed and you need instant results from the local database. Use rpm -qpl when you have a downloaded .rpm file and want to audit it without installing. Use dnf repoquery -l when you need to check repository packages, resolve modular streams, or search across multiple enabled repos. Use rpm -qf when you have a file path and need to find the owning package. Use ls -Z combined with rpm -ql when you need to verify SELinux contexts alongside file paths. Stick to rpm for low-level inspection and dnf for repository-aware queries.

Where to go next