I recently setup this blog on Digital Ocean using nginx. Sometime in the past week I found that some evil sites are hotlinking my image of pink floyd album covers. Image hotlinking is when someone serves media stored on your server directly on their webpage - hence stealing bandwidth. It is not cool!

nginx makes it very easy to stop image hotlink. There are several pages on the internet which talk about it.

Requirements

I wanted generic solution which would work for all my domains, without changing each of their configs (sites-enabled)

Rewrite and display a different image (Shame the hotlinker? Get free publicity?) in place of the hotlinked media

Must play nice with Google/Other image search engines - instead of stopping hotlink always

Solution

location ~ * \.(gif|png|jpe?g) $ { expires 7d ; add_header Pragma public ; add_header Cache-Control "public, must-revalidate, proxy-revalidate" ; # prevent hotlink valid_referers none blocked ~ .google. ~ .bing. ~ .yahoo. server_names ~ ($host); if ( $invalid_referer ) { rewrite (.*) /static/images/hotlink-denied.jpg redirect ; # drop the 'redirect' flag for redirect without URL change (internal rewrite) } } # stop hotlink loop location = /static/images/hotlink-denied.jpg { }

Details

~ is used for case sensitive matching while ~* is for case insensitive matching.

is used for case sensitive matching while is for case insensitive matching. nginx checks locations given by regular expression in the order listed in the configuration file - more here. This means that cache headers for media and image hotlinking prevention have to be in the same block!

location = /static/images/hotlink-denied.jpg { } is required to prevent infinite loop that happens: Evil site requests A.jpg -> redirect request to B.jpg -> Evil site requests B.jpg -> redirect reqest to B.jpg -> ... . nginx first searches for the most specific prefix location given by literal strings regardless of the listed order - more here.

is required to prevent infinite loop that happens: . nginx first searches for the most specific prefix location given by literal strings regardless of the listed order - more here. $host is a variable that makes the solution generic - it will serve http://$host/static/images/hotlink-denied.jpg for each domain. We just need to place different image in the same path on all domains.

is a variable that makes the solution generic - it will serve for each domain. We just need to place different image in the same path on all domains. The rewrite module always sends 302 Found response when either: [1] rewrite flag is specified [2] or when the URL being redirected to starts with http:// . This means that the URL shown in browser will change to the redirected URL. If you want an redirect without URL change (internal rewrite, with no 302), then just drop the redirect flag in the rewrite. These two links helped me learn that.

response when either: [1] flag is specified [2] or when the URL being redirected to starts with .

Testing