1. Introduction

Most Linux distributions use package management systems to install and remove software. In the case of Debian, these systems are apt (or aptitude) and dpkg. Software from these systems comes within packages in the form of .deb files: compressed archives containing executables, libraries, configuration files, and sometimes documentation. They act as a neat way to bundle everything a program needs.

Since packages usually employ versioning, the most recent version is usually recommended. However, scenarios such as a new update introducing bugs or removing features might require us to install an older package version. Although installing an older version may provide a temporary solution, package managers prioritize the latest available version during upgrades. Hence, upgrading to the latest version might reinstall the issues introduced in the new release.

So, to prevent automatic upgrades and maintain a specific version, Debian enables us to pin packages, controlling their update behavior. Pinning instructs the package manager to ignore any newer versions for that particular package.

In this tutorial, we’ll explore how to install and pin an older version of a .deb package. All commands in this tutorial have been tested on the Debian-based Ubuntu 22.04.

2. Understanding Package Versions and Dependencies

The name of a .deb package itself contains the version information. It’s made up of two parts: package_name and version.

The package_name is the actual name of the software program within the package, e.g. firefox, apache2. Software versions within a package denote the specific release included. This format involves three parts separated by hyphens or colons:

  • Upstream Version (e.g., 9.0.1): This is the main version number assigned by the original developer or project.
  • (optional) Debian Revision (e.g., ~2-ubuntu1): If present, this part indicates changes made for the Debian distribution and might include bug fixes, security updates, or packaging modifications.
  • Epoch (Rare, e.g., 1): This is an uncommon part used in specific situations to handle major versioning scheme changes within a package.

For instance, a package named firefox_10.0.1-1.deb would contain version 10.0.1 of the Firefox software with a Debian-specific revision of 1. This revision might indicate bug fixes or other modifications made for the particular Debian release we’re using.

We can use the apt-cache policy command on Ubuntu to inspect the available versions of a package within the configured repositories. Let’s use it with the wget package:

$ sudo apt-cache policy wget
wget:
  Installed: (none)
  Candidate: 1.21.2-2ubuntu1.1
  Version table:
     1.21.2-2ubuntu1.1 500
        500 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages
        500 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages
     1.21.2-2ubuntu1 500
        500 http://archive.ubuntu.com/ubuntu jammy/main amd64 Packages
        100 /var/lib/dpkg/status

The command’s output indicates that wget isn’t installed on the machine. The Version table lists the available packages and their priorities. We can observe that the 1.21.2-2ubuntu1 is the older version of the package.

Therefore, we can install the older versions of a package using several methods:

  • apt-get command
  • package cache
  • building from source code

Notably, installing older versions of packages can introduce compatibility issues or security vulnerabilities.

3. Using the apt-get Command

To begin with, we can install older versions that apt-cache knows about using the apt-get install command:

$ sudo apt-get install wget=1.21.2-2ubuntu1
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  wget
0 upgraded, 1 newly installed, 0 to remove and 2 not upgraded.
...

This command installs the older wget package with the version number 1.21.2-2ubuntu1.

Although apt-get install enables installing older versions, its effectiveness is limited. By default, package repositories prioritize storing the latest releases, potentially making older versions unavailable.

4. Using the Local Package Cache

The apt package manager creates a local cache to store downloaded packages. This cache resides in the /var/cache/apt/archives/ directory. This directory stores .deb files of packages downloaded by apt either for installation or as part of an upgrade process.

These cached packages are useful for offline installations and restoring older versions of packages after accidental upgrades or maintenance.

For this example, we can see the wget package with its available versions cached locally:

$ ls -l /var/cache/apt/archives/
...
-rw-r--r-- 1 root root  1733874 May  3 07:10 vim_2%3a8.2.3995-1ubuntu2.17_amd64.deb
-rw-r--r-- 1 root root   338506 Jun 26 13:27 wget_1.21.2-2ubuntu1.1_amd64.deb
-rw-r--r-- 1 root root   367378 Jan 24  2022 wget_1.21.2-2ubuntu1_amd64.deb
-rw-r--r-- 1 root root    53702 May  3 07:10 xxd_2%3a8.2.3995-1ubuntu2.17_amd64.deb
...

Again, using the cached .deb file, we can install the older wget package with version 1.21.2-2ubuntu1 with dpkg:

$ sudo dpkg -i /var/cache/apt/archives/wget_1.21.2-2ubuntu1_amd64.deb
Selecting previously unselected package wget.
(Reading database ... 93831 files and directories currently installed.)
Preparing to unpack .../wget_1.21.2-2ubuntu1_amd64.deb ...
Unpacking wget (1.21.2-2ubuntu1) ...
Setting up wget (1.21.2-2ubuntu1) ...
Processing triggers for install-info (6.8-4build1) ...
Processing triggers for man-db (2.10.2-1) ...

Thus, we end up with an older version of wget from a locally cached .deb file.

5. Building From Source

Another often better alternative to installing older versions of packages is building the package directly from its source code. This method usually provides better flexibility in the choice of versions.

For this example, we install an older version of the wget package, version 1.20.3, directly from the source code.

To begin, let’s download the source code ZIP file via the ftp command:

$ ftp https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz
Trying 209.51.188.20:443 ...
Requesting https://ftp.gnu.org/gnu/wget/wget-1.20.3.tar.gz
100% |*****************************************************************************************|  4384 KiB  459.09 KiB/s    00:00 ETA
4489249 bytes retrieved in 00:09 (459.09 KiB/s)

Let’s decompress the resulting file:

$ tar xvzf wget-1.20.3.tar.gz
wget-1.20.3/build-aux/missing
wget-1.20.3/build-aux/texinfo.tex
wget-1.20.3/build-aux/gnupload
...

Before moving on with the installation, we install the libssl-dev, build-essential, pkg-config, and libgnutls28-dev packages, as they are the package dependencies:

$ sudo apt-get update && apt-get install libssl-dev build-essential pkg-config libgnutls28-dev

Now, we should be able to configure the sources successfully:

$ cd wget-1.20.3/
$ ./configure
configure: configuring for GNU Wget 1.20.3
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
...

Upon completion, let’s build and install the wget executable:

$ make && sudo make install

This command installs the wget executable and makes it ready for use:

$ wget --help
GNU Wget 1.20.3, a non-interactive network retriever.
Usage: wget [OPTION]... [URL]...

Mandatory arguments to long options are mandatory for short options too.

Startup:
  -V,  --version                   display the version of Wget and exit
  -h,  --help                      print this help
...

Hence, we’ve successfully installed the older version of the wget package by building it from its sources.

6. Pinning a Package

Package pinning is a method used by package management systems to prevent certain packages from being automatically updated to new versions. This is beneficial for maintaining system stability and ensuring compatibility. Also, it’s useful for testing or rollback strategies.

Pinning packages reduces the risk of unforeseen issues caused by automatic updates. Debian offers several methods for pinning specific package versions:

  • /etc/apt/preferences.d/ file
  • apt-mark hold command
  • aptitude hold command
  • dpkg –set-selections command

In the sections below, we explore each method.

6.1. /etc/apt/preferences.d/ File

The /etc/preferences.d/ file enables us to set preferences for specific packages, influencing how apt handles them during installation, upgrades, or other operations.

The files use a simple text format to specify preferences, usually including several fields:

  • Package: package name
  • Pin: preference type, e.g., version, origin
  • Pin-Priority: number, indicating the preference level

For this example, we pin the wget package with version 1.21.2-2ubuntu1.

Let’s create and populate the wget file in /etc/apt/preferences.d/ directory:

$ sudo cat /etc/apt/preferences.d/wget
Package: wget
Pin: version 1.21.2-2ubuntu1
Pin-Priority: 999

The above preferences instruct the apt package manager to prefer version 1.21.2-2ubuntu1 of the wget package. This should prevent it from automatic upgrades to newer versions.

6.2. apt-mark hold Command

The apt-mark hold command marks a package as held back, which prevents it from being automatically upgraded when we run the apt upgrade command.

Let’s see the basic syntax:

$ sudo apt-mark hold <package_name>

Now, we can use the apt-mark hold command on the wget package:

$ sudo apt-mark hold wget
wget set on hold.

As seen above, unlike the preferences method, we can’t specify a particular version to hold. It effectively locks the installed version, preventing automatic upgrades.

We list all packages held back packages with the showhold command:

$ apt-mark showhold
wget

Additionally, to reinstate automatic upgrades for a held package, we use the unhold command:

$ sudo apt-mark unhold <package>

Hence, we successfully leveraged the apt-mark hold command to prevent the currently installed version of a package from being automatically upgraded.

6.3. aptitude hold Command

The aptitude hold command is similar to the apt-mark hold command, but it uses the Aptitude package manager instead of apt.

Of course, it has a usage syntax similar to the apt-mark hold command:

$ sudo aptitude hold <package_name>

Unlike apt, the aptitude package manager is rarely installed by default on Debian-based systems. Let’s see how to install it:

$ sudo apt install aptitude

Now, we should be able to hold back the wget package with the aptitude command:

$ sudo aptitude hold wget

However, to check for held packages, we use the search subcommand:

$ aptitude search '~ahold'
ih  wget                                                       - retrieves files from the web       

Similarly, we can enable a package for upgrade via unhold:

$ sudo aptitude unhold <package>

Thus, we used the aptitude hold command to prevent automatic upgrades to the version of a package.

6.4. dpkg –set-selections Command

There is also a low-level approach using dpkg to pin packages by reading package selections from stdin.

The format is fairly simple and involves and  where the state is set to hold:

$ echo "<package> hold" | sudo dpkg --set-selections

Using the above syntax, let’s set the wget package to the hold state:

$ echo "wget hold" | sudo dpkg --set-selections

After pinning the wget package, we can verify the selection via the —get-selections option:

$ dpkg --get-selections | grep hold
wget                        hold

Additionally, to remove the hold over a package, we set the state to install:

$ echo "<package> install" | sudo dpkg --set-selections

Hence, we’ve pinned a package version by piping the package name along with the state to dpkg –set-selections command.

7. Conclusion

In this article, we learned how to install older versions of Debian packages using apt-get, package cache, or directly from the source code. We also learned how to pin these package versions using a preferences file, dpkg, and hold commands made available through the apt and aptitude package managers.

As mentioned in the article, the apt preferences file offers fine-grained control over package versions and priorities, which is often ideal for complex environments requiring detailed management. On the other hand, the apt-mark and aptitude hold commands provide a more user-friendly method leveraging the respective package managers. However, for standard scripting and batch operations, the dpkg –set-selections command is often more viable.