“HTTP secure link” using OpenResty Nginx module

Whenever you generate a link on your website to let your visitor download some content, you run into the risk that people will start sharing that link with others. If you want to discourage that you can require the user to login before downloading. If this is not an option for you then you can generate links that are only valid for a few seconds. This is a feature that Nginx supports using the Nginx module HttpSecureLinkModule.

Standard HttpSecureLinkModule

You can specify a Unix timestamp on which the link should no longer be valid; you add this timestamp to the query string like this:


In this URL (that uses ‘GET’ parameters) the ‘e’ parameter is the expiry timestamp and the ‘s’ parameter is the signature. This signature is calculated by taking a secret and appending the expiry timestamp to it and calculating a hash over that.

$signature = md5( $secret . $path . $expiry );

OpenResty Nginx modules

But what if you want to use a variant on that? For instance, if you want to restrict on certain networks or use a different hashing algorithm for the signature? My idea was to use the excellent building blocks from the set-misc-nginx-module that is made by Yichun Zhang (a.k.a. “agentzh”). This Nginx module is not included in Nginx by default, but is a part of the OpenResty Nginx distribution or as Yichun Zhang explaines:

By taking advantage of various well-designed Nginx modules, OpenResty effectively turns the nginx server into a powerful web app server. OpenResty is not an Nginx fork. It is just a software bundle. – openresty.org

We did 3 pull requests to the “set-misc-nginx-module” that allow for the needed functionality. You can compile this Nginx module into your Nginx by pointing the “–add-module” configure option to your checkout of this module. We have added the “set_encode_base64url”, “set_decode_base64url”, “set_expired” and “set_ip_matches” directives for building a custom secure link Nginx module.

Installation instructions

The configuration for the Nginx module would be like this:

# GET /?e=1893452400&n=MC4wLjAuMC8w&s=QVvNrcn-j4smvOFhuPTUU7EgoI8
# returns 403 when signature on the arguments is not correct OR
# when expire time is passed or network does not match.
# It is an alternative to the HttpSecureLinkModule in Nginx.
# This example has expiry "2030-01-01" and network "".
location / {
  set_hmac_sha1 $signature 'secret-key' "e=$arg_e&n=$arg_n";
  set_encode_base64url $signature;
  if ($signature != $arg_s) {
    return 403;
  set_expired $expired $arg_e;
  if ($expired) {
    return 403;
  set_decode_base64url $network $arg_n;
  set_ip_matches $ip_matches $network $remote_addr;
  if ($ip_matches = 0) {
    return 403;
  root /var/www;

Where the PHP code to generate the link would be:

function base64url_encode($s)
{ return rtrim(strtr(base64_encode($s),"+/", "-_"),"=");
$secret = "secret-key";
$expiry = strtotime("2030-01-01 00:00:00");
$network = base64url_encode("");
$query = "e=$expiry&n=$network";
$signature = base64url_encode(hash_hmac('sha1',$query,$secret,true));
$url = "http://localhost/?$query&s=$signature";
echo "$url\n";

Output would be: