Ubuntu 16.04 Create LAN Repository with Reprepro

Unless you work in a homogeneously Linux shop, you’re unlikely to even think about setting up your own local repository where your internal servers can grab .deb packages from. The package manager apt-get searches for, downloads, and installs the package for you. A repository in Linux terms is simply the collection of packages, but almost nothing in the *nix world is as simple as it seems. Especially when you have the sometimes unwanted personality trait called perfectionist. A repository can be viewed as the same concept as a caching proxy server, except it is for application packages that you would like to maintain and make available inside your internal network. You can make it public as well, but my article will focus on the use case of creating an internal repository for Ubuntu 16.04 (codename Xenial).

Whenever I determine a task somewhat hard to accomplish due to a lack of resources through search engines like Google, I like to document my work and post it as a blog on my website. Sometimes I even use my own website as reference material because I after a while of not using some knowledge, it inevitably gets tossed into the garbage. How long does it take you to forget material after not using it? Or maybe it’s the early onset of Alzheimer’s


  • Two Ubuntu 16.04 LTS virtual machines
  • Patience

By the end of the guide you will have:

  • Prepared and published a repository signing key
  • Set up a repository with Reprepro, the repository manager
  • Made the repository accessible to internal servers with NGINX
  • Added the repository on another server
  • Tested pulling and installing a package from your own local repo

First, we need a valid package signing key. This step is crucial for a secure repository, since we will be digitally signing all the packages. Package signing gives the downloader confidence that the source can be trusted.

In this section, you will generate an encrypted master public key and a signing sub-key by following these steps:

  • Generate a Master Key
  • Generate a Subkey for Package Signing
  • Detach Master Key from Subkey

Let’s make the master key. This key should be kept safe and secure since this is what people will be trusting.

Before we begin, let’s install rng-tools though apt-get:

apt-get install rng-tools

GPG requires random data, called entropy, to generate keys. Entropy is normally generated over time by the Linux kernel and stored in a pool. However, on cloud servers, the kernel may have trouble generating the amount of entropy required by GPG. To help the kernel, we install the rngd program (found in the rng-tools package). This program will ask the host server for entropy. Once retrieved, rngd will add the data to the entropy pool to be used by other applications like GPG.

If you get a message like this:

Trying to create /dev/hwrng device inode...
Starting Hardware RNG entropy gatherer daemon: (failed).
invoke-rc.d: initscript rng-tools, action "start" failed.
Start the rngd daemon manually with:
rngd -r /dev/urandom

By default rngd looks for a special device to retrieve entropy from /dev/hwrng. Some Virtual machines do not have this device. To compensate we use the pseudo random device /dev/urandom by specifying the -r directive. For more information, you can check out the rng-tools wiki page.

Now that we have a pool of entropy, we can generate the master key. Do this by invoking the command gpg. You will see a prompt similar to the following:

gpg --gen-key
gpg (GnuPG) 1.4.16; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.


Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? 1

Specify the first option, “RSA and RSA (default)” 1, in the prompt. Selecting this will have gpg generate first a signing key, then a encryption subkey (both using the RSA algorithm). We don’t need an encryption key for this tutorial, but as a great person once said, “why not?” There is no disadvantage in having both, and you may use the key for encryption in the future.

Hit Enter and you’ll be prompted for a keysize:

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096

The key size correlates directly to how secure you want your master key to be. The higher the bit size, the more secure key. The Debian project recommends using 4096 bits for any signing key, so I would specify 4096 here. For the next 2-5 years the default bit size 2048 is sufficient if you’d rather use that. A size of 1024 is uncomfortably close to being unsafe and should not be used.

Press Enter for the expire prompt.

Please specify how long the key should be valid.
0 = key does not expire
<n>  = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 0

Master keys do not normally have expiration dates, but set this value as long as you expect to use this key. If you only plan to use this repository for only the next 6 months you can specify 6m. 0 will make it valid forever.

Hit Enter, then y. You will be prompted to generate a user ID. This information will be used by others and yourself to identify this key – so use real information!

You need a user ID to identify your key; the software constructs the user ID

from the Real Name, Comment and Email Address in this form:

"First Last (First Last) <firstlast@company.com>"
Real name: Travis Runyard
Email address: travis.runyard@example.com
You selected this USER-ID:
"Travis Runyard <travis.runyard@example.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o

If the information is correct, hit o and Enter. We need to add a password to ensure that only you have access to this key. Make sure to memorize this password since there is no way to recover a gpg key password (a good thing).

You need a Passphrase to protect your secret key.
Enter passphrase: (hidden)
Repeat passphrase: (hidden)

Now for some magic (math) to happen. This might take a little while, so sit back or get a cup of your favorite drink.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
Not enough random bytes available.  Please do some other work to give
the OS a chance to collect more entropy! (Need 300 more bytes)
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: /root/.gnupg/trustdb.gpg: trustdb created
gpg: key 10E6133F marked as ultimately trusted
public and secret key created and signed.
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   4096R/10E6133F 2014-08-16
Key fingerprint = 1CD3 22ED 54B8 694A 0975  7164 6C1D 28A0 10E6 133F
uid                  Travis Runyard <travis.runyard@example.com>
sub   4096R/7B34E07C 2014-08-16

Now we have a master key. The output shows that we created a master key for signing (`0E6133F on the pub line above). Your key will have different IDs. Make note of your signing key’s ID (the example uses 10E6133F). We’ll need that information in the next steps when creating another subkey for signing.

Generate a Subkey for Package Signing

Now we’ll create a second signing key so that we don’t need the master key on this server. Think of the master key as the root authority that gives authority to subkeys. If a user trusts the master key, trust in a subkey is implied.

In the terminal execute:

gpg --edit-key 10E6133F

Replace the example ID with your key’s ID. This command enters us into the GPG environment. Here we can edit our new key and add a subkey. You’ll see the following output:

gpg (GnuPG) 1.4.16; Copyright (C) 2013 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Secret key is available.
pub  4096R/10E6133F  created: 2014-08-16  expires: never       usage: SC
trust: ultimate      validity: ultimate
sub  4096R/7B34E07C  created: 2014-08-16  expires: never       usage: E
[ultimate] (1). Travis Runyard <travis.runyard@example.com>

At the prompt, type addkey:


Press Enter. GPG will prompt for your password. Enter the password that you used to encrypt this key.

Key is protected.
You need a passphrase to unlock the secret key for
user: "Travis Runyard <travis.runyard@example.com>"
4096-bit RSA key, ID 10E6133F, created 2014-08-16
gpg: gpg-agent is not available in this session
Enter passphrase: <hidden>

You will see the following prompt for key type.

Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
Your selection? 4

We want to create a signing subkey, so select “RSA (sign only)” 4. RSA is faster for the client, while DSA is faster for the server. We’re picking RSA in this case because, for every signature that we make on a package, possibly hundreds of clients will need to verify it. The two types are equally secure.

Again we are prompted for a key size.

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096

This tutorial uses 4096 for increased security.

Please specify how long the key should be valid.
0 = key does not expire
<n>  = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 1y

We already have a master key, so the expiration time for the subkey is less important. One year is a good time frame.

Hit Enter, and then type y (yes) twice for the next two prompts. Some math will generate another key.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
pub  4096R/10E6133F  created: 2014-08-16  expires: never       usage: SC
trust: ultimate      validity: ultimate
sub  4096R/7B34E07C  created: 2014-08-16  expires: never       usage: E
sub  4096R/A72DB3EF  created: 2014-08-16  expires: 2015-08-16  usage: S
[ultimate] (1). Travis Runyard <travis.runyard@example.com>

Type save at the prompt.


In the output above, the SC from our master key tells us that the key is only for signing and certification. The E means the key may only be used for encryption. Our signing key can be correctly seen with only the S.

Note your new signing key’s ID (the example shows A72DB3EF on the second sub line above). Your key’s ID will be different.

Enter save to return to the terminal and to save your new key.


Detach Master Key From Subkey

The point of creating the subkey is so we don’t need the master key on our server, which makes it more secure. Now we’ll detach our master key from our subkey. We will need to export the master key and subkey, then delete the keys from GPG’s storage, then re-import just the subkey.

First let’s use the –export-secret-key and –export commands to export the whole key. Remember to use your master key’s ID!

gpg --export-secret-key 10E6133F > private.key
gpg --export 10E6133F >> private.key

By default –export-secret-key and –export will print the key to our console, so instead we pipe the output to a new file (private.key). Make sure to specify your own master key ID, as noted above.

Important: Make a copy of the private.key file somewhere safe (not on the server). Possible locations are on a floppy disk or USB drive. This file contains your private key, your public key, your encryption subkey, and your signing subkey.

After you have backed up this file to a safe location, delete the file:

Back up the private.key file before running this:

rm private.key

Now export your public key and your subkey. Make sure to change the IDs to match the master key and the second subkey that you generated (don’t use the first subkey).

gpg --export 10E6133F > public.key
gpg --export-secret-subkeys A72DB3EF > signing.key

Now that we have a backup of our keys we can remove our master key from our server.

gpg --delete-secret-key 10E6133F

Re-import only our signing subkey.

gpg --import public.key signing.key

Check that we no longer have our master key on our server:

gpg --list-secret-keyssec#  4096R/10E6133F 2014-08-16
uid                  Travis Runyard <travis.runyard@example.com>
ssb   4096R/7B34E07C 2014-08-16
ssb   4096R/A72DB3EF 2014-08-16

Notice the # after sec. This means our master key is not installed. The server contains only our signing subkey.

Clean up your keys:

rm public.key signing.key

The last thing you need to do is publish your signing key.

gpg --keyserver keyserver.ubuntu.com --send-key 10E6133F

This command publishes your key to a public storehouse of keys – in this case Ubuntu’s own key server. This allows others to download your key and easily verify your packages.

Set Up a Repository Using Reprepro

Now let’s get to the point of this tutorial: creating an apt-get repository. Apt-get repositories are not the easiest things to manage. Thankfully R. Bernhard created Reprepro, who used to “produce, manage and sync a local repository of Debian packages” (also known as Mirrorer). Reprepro is under the GNU licence and completely open source.

Install and Configure Reprepro

Reprepro can be installed from the default Ubuntu repositories.

apt-get update
apt-get install reprepro

Configuration for Reprepro is repository-specific, meaning you can have different configurations if you make multiple repositories. Let’s first make a home for our repository.

Make a dedicated folder for this repository and move to it

mkdir -p /var/repositories/
cd /var/repositories/

Create the configuration directory.

mkdir conf
cd conf/

Create two empty config files (options and distributions).

touch options distributions

Open up the options file in your favorite text editor (vi is installed by default but to get full functionality install vim).

vi options

This file contains options for Reprepro and will be read every time Reprepro runs. There are several options that you can specify here. See the manual for the other options.

In your text editor add the following by pressing the letter i


Press escape then type :wq then hit enter. This will save our changes and return to the console.

The ask-passphrase directive tells Reprepro to request a GPG password when signing. If we don’t add this to the options Reprepro will die if our key is encrypted (it is).

Open the distributions file.

vi distributions

This file has four required directives. Add these to the file:

Codename: xenial
Components: main
Architectures: i386 amd64
SignWith: A72DB3EF

The Codename directive directly relates to the code name of the released Debian distributions and is required. This is the code name for the distribution that will be downloading packages, and doesn’t necessarily have to match the distribution of this server. For example, the Ubuntu 16.04 LTS release is called xenial, Ubuntu 14.04 LTS is called trusty, Ubuntu 12.04 LTS is called precise, and Debian 7.6 is known as wheezy. This repository is for Ubuntu 16.04 LTS so xenial should be set here.

The Components field is required. This is only a simple repository so set main here. There are other namespaces such as “non−free” or “contrib” – refer to apt-get for proper naming schemes.

Architectures is another required field. This field lists binary architectures within this repository separated by spaces. This repository will be hosting packages for 32-bit and 64-bit servers, so i386 amd64 is set here. Add or remove architectures as you need them.

To specify how other computers will verify our packages we use the SignWith directive. This is an optional directive, but required for signing. The signing key earlier in this example had the ID A72DB3EF, so that is set here. Change this field to match the subkey’s ID that you generated.

Save and exit from the file by pressing escape then :wq and Enter.

You have now set up the required structure for Reprepro.

Add a Package(s) with Reprepro

First let’s change our directory to a temporary location.

mkdir -p /tmp/debs
cd /tmp/debs

We need some example packages to work with – wget them with:

wget https://github.com/Silvenga/examples/raw/master/example-helloworld_1.0.0.0_amd64.deb
wget https://github.com/Silvenga/examples/raw/master/example-helloworld_1.0.0.0_i386.deb

These packages contain a simple bash script to prove the functionality of our repository. If you would prefer to simply move sync/copy all the .deb’s already cached on your computer use this command:

rsync -av /var/cache/apt/archives/ /tmp/debs/

Running the program ls should give us this layout:

example-helloworld_1.0.0.0_amd64.deb  example-helloworld_1.0.0.0_i386.deb

We now have two example packages. One for 32-bit (i386) computers, another for 64-bit (amd64) computers. You can add them both to our repository with:

reprepro -b /var/repositories includedeb xenial example-helloworld_1.0.0.0_*

The -b argument specifies the “(b)ase” directory for the repository. The includedeb command requires two arguments – < distribution code name > and < file path(s) >. Reprepro will prompt for our subkey passcode twice. If you want to specify all deb files in the directory, simply use *.deb.

Exporting indices...
C3D099E3A72DB3EF Travis Runyard <travis.runyard@example.com> needs a passphrase
Please enter passphrase: < hidden >
C3D099E3A72DB3EF Travis Runyard <travis.runyard@example.com> needs a passphrase
Please enter passphrase: < hidden >

Listing and Deleting

We can list the managed packages with the list command followed by the codename. For example:

reprepro -b /var/repositories/ list xenial
xenial|main|i386: example-helloworld
xenial|main|amd64: example-helloworld

To delete a package, use the remove command. The remove command requires the codename of the package, and the package name. For example:

reprepro -b /var/repositories/ remove xenial example-helloworld

Make the Repository Public

We now have a local package repository with a couple of packages. Next, we’ll install Nginx as a web server to make this repository available to your internal network.

Install Nginx

apt-get update
apt-get install nginx

Nginx comes installed with a default example configuration. Make a copy of the file in case you want to look at it at another time.

mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak
touch /etc/nginx/sites-available/default

Now that we have an empty configuration file, we can start configuring our Nginx server to host our new repository.

Open the configuration file with your favorite text editor.

vi /etc/nginx/sites-available/default

And add the following configuration directives:

server {
listen 80 default_server;
listen [::]:80 default_server;
## Let your repository be the root directory
root        /var/repositories;
## Always good to log
access_log  /var/log/nginx/repo.access.log;
error_log   /var/log/nginx/repo.error.log;
location / {
index index.php index.html;
autoindex on;
## Prevent access to Reprepro's files
location ~ /(db|conf) {
deny        all;
return      404;

Nginx has some pretty sane defaults. All we needed to configure was the root directory, while denying access to Reprepro’s files. See the in-line comments for more details.

Restart the Nginx service to load these new configurations.

service nginx restart

Or you can simply reload the configuration without restarting the daemon.

nginx –s reload

Your public Ubuntu repository is ready to use!

You’ll need your Virtual machine’s IP address to let users know the location of the repository. If you don’t know your Virtual machine’s public address you can find it with ifconfig.

ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 04:01:23:f9:0e:01
inet addr:  Bcast:  Mask:
inet6 addr: fe80::601:23ff:fef9:e01/64 Scope:Link
RX packets:16555 errors:0 dropped:0 overruns:0 frame:0
TX packets:16815 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:7788170 (7.7 MB)  TX bytes:3058446 (3.0 MB)

In the above example, the server’s address is Yours will be different.

With your Reprepro server’s IP address, you can now add this repository to any other appropriate server.

Install a Package from Our New Repository

If you haven’t already, spin up another Virtual machine with Ubuntu 16.04 LTS, so that you can do a test installation from your new repository.

On the new server, download your public key to verify the packages from your repository. Recall that you published your key to keyserver.ubuntu.com.

This is done with the apt-key command.

apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 10E6133F

This command downloads the specified key and adds the key to the apt-get database. The adv command tells apt-key to use GPG to download the key. The other two arguments are passed directly to GPG. Since you uploaded your key to “keyserver.ubuntu.com” use the –keyserver keyserver.ubuntu.com directive to retrived the key from the same location. The –recv-keys <key ID> directive specifies the exact key to add.

Now add the repository’s address for apt-get to find. You’ll need your repository server’s IP address from the previous step. This is easily done with the add-apt-repository program.

add-apt-repository "deb xenial main"

Note the string that we give add-apt-repository. Most Debian repositories can be added with the following general format:

deb (repository location) (current distribution code name)  (the components name)

The repository location should be set to the location of your server. We have an HTTP server so the protocol is http://. The example’s location was Our server’s code name is xenial. This is a simple repository, so we called the component “main”. You can also simply append the text inside quotations at the end of the /etc/apt/sources.list file

After we add the repository, make sure to run an apt-get update. This command will check all the known repositories for updates and changes (including the one you just made).

apt-get update

After updating apt-get, you can now install the example package from your repository. Use the apt-get command normally.

apt-get install example-helloworld

If everything is successful you can now execute example-helloworld and see:

Hello, World!
This package was successfully installed!
Congratulations! You have just installed a package from the repository that you created!

To remove the example package, run this command:

apt-get remove example-helloworld

This removes the example package that you just installed.

Disqus Comments Loading...

Recent Posts

Bittorrent IP Blocklists

In addition to using a VPN service, as an extra precaution I've been using the blocklist feature of my bittorrent… Read More

October 26, 2019 3:31 pm

FreeNAS Error Creating Pool

command '('gpart', 'create', '-s', 'gpt', '/dev/da8')' returned non-zero exit status 1. If you get this error while trying to create… Read More

June 7, 2019 3:44 pm

Change Grub Default Boot Entry on Linux Mint

I'm dual booting Windows and Linux Mint on my laptop. The grub default is to boot into Linux Mint, however… Read More

April 23, 2019 7:45 pm

How to Reset Secure Channel On Active Directory Domain Controller

When you're a little too careless about virtualizing your domain controllers, cloning, migrating, backing up and restoring, returning from vacation… Read More

April 21, 2019 8:14 am

Run Systemd Script Before System Shutdown

I tried to retain the NGINX FastCGI cache and have it persist across system reboots instead of being ephemeral by… Read More

April 20, 2019 10:14 am

Learn Systemctl Usage to Manage Systemd Service in Linux

Systemd is new service manager for Linux. It's a replacement for all previous init systems (SysV/SysVinit & Ubuntu's Upstart) and… Read More

April 20, 2019 7:55 am