Setup DDNS/DynDNS in OpenWrt

I serving my small homepage directly from my router with OpenWrt Linux — thus I don’t have to pay for any hosting because the router is anyway always online. My provider gives to my router some public IP which is changes sometimes: maybe about once per week. That is fine for me but I have to change it manually. Of course I can buy a static public IP from my internet provider but my goal is to have cheapest as possible website. So I need to automatically and periodically update the DNS A record with my current IP.

To solve this problem people uses Dynamic DNS (DDNS) which is de facto some pseudo protocol when router itself constantly registers it’s current IP on the DNS server. Most routers already have support of some DDNS providers where most popular are and or even manufactures like ASUS may have their own DDNS. Gamers and owners of IP cameras very often using this.

Unfortunately my DNS registrar doesn’t support DDNS protocol so I have to use some another. A good news is that OpenWrt already have a package ddns-scripts witch supports a lot of servers. I checked almost all DDNS providers that are supported my and choose looks like one of the first DDNS providers and some other even tries to implement it’s API. But it’s paid and that’s not acceptable for me to because with the same money I just can buy a static IP. The have some strange API problems with refreshing IP so there is even a separate script for OpenWrt ddns-scripts_no-ip_com. In the same time DuckDNS looks like was made by programmers for programmers. It allows to quickly register with Google account then they give you a generated random token instead of password and they have a good documentation.

So the API is so simple that I even was wondered why it was created the ddns-scripts package. In fact, all what you need to do is to register on DuckDNS and receive your token (i.e. password) then login to your OpenWrt LUCI admin panel, then open System / Scheduled Tasks and add the following line:

* */4 * * * wget -4 -q -O /dev/null{YOURDOMAIN}/{YOURTOKEN}

i.e. each 4 hours you will send a HTTP get request to DuckDNS.

Then you can check logs of cron task in syslogs: System / System Logs. For example for my domain

Mon Apr 22 18:52:00 2019 crond[12903]: USER root pid 14005 cmd wget -4 -q -O /dev/null

But for some reason this setup via Luci doesn’t worked for me so better to do the same with command line. Login and edit crontab:

ssh root@
root@OpenWrt:~# echo "42 */4 * * * /etc/" >> /etc/crontabs/root

or you can edit:

root@OpenWrt:~# crontab -e

The crontab -e opens vi editor for /etc/crontabs/root. Also note that I enabled the cron service just to be sure. See OpenWrt cron documentation for details.

Now put there a line like this:

42 */4 * * * /etc/

Note here that I added some random minute 42 to keep DuckDNS from requests waves if all users tries to update their DNS once in a hour. So please take some another minute too.

Then add this script:

wget -4 -q -O /dev/null{YOURDOMAIN}/{YOURTOKEN}

to /etc/ and chmod +x it.

Now you need to enable and restart cron service:

root@OpenWrt:~# /etc/init.d/cron enable
root@OpenWrt:~# /etc/init.d/cron restart
root@OpenWrt:~# logread | grep cron

The last command is useful to see cron logs. You may want to increase cronloglevel in /etc/config/system. If everything worked then in duckdns dashboard you’ll IP will be updated. See "Last time changed" field.

Then your router will be accessible with the new domain. For example for my domain

$ dig

; DiG
;; global options: +cmd
;; Got answer:
;; HEADER   HEADER- opcode: QUERY, status: NOERROR, id: 41868
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 4096
;		IN	A


;; Query time: 212 msec
;; WHEN: Mon Apr 22 23:55:45 EEST 2019
;; MSG SIZE  rcvd: 129

Here you can see that DNS server (BTW that’s AdGuard) responded that IP of the domain is i.e. my public IP.

Use regular domain as alias for DDNS

I already have a domain and I would like to use it instead of the DDNS DNS supports this and what I need to do is to add to my domain a new record CNAME with the DDNS But DNS spec allows this only for subdomains. I.e. I can map to the but I can’t do that for for root domain Not sure why but most domain registrants follow the rule. I added a subdomain record and mapped via CNAME to and here is how it resolved now:

$ dig
; DiG 9.11.5-P1-1ubuntu2.3-Ubuntu
;; global options: +cmd
;; Got answer:
;; HEADER opcode: QUERY, status: NOERROR, id: 19506
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 4096
;		IN	A


;; Query time: 223 msec
;; WHEN: Sun May 05 14:22:29 EEST 2019
;; MSG SIZE  rcvd: 133

You can see that was firstly resolved to CNAME which then was resolved to my router’s IP This have a downside that now your router’s IP is visible to anyone who would like to hack you.

Fortunately I using CloudFlare which works like a proxy that protects my site from DDoS. It’s free plan allows almost everything that I need. But what is important that I can transfer my domain to CF nameserver and CF allows to map CNAME to root domain So in CF DNS Settings I set the CNAME and now when I try to open then it opened my website from the router. In fact, they don’t do a real alias and domain refers to CF IP address but internally they proxy HTTP requests to

CludFlare DNS settings screenshot
CludFlare DNS settings screenshot

So I configured these domains:

  1. is a CNAME to and please note that the cloud icon is gray which means that CF will not proxy this domain and it will work only as DNS. Thus the will be always resolved to my router’s IP via DDNS as you already saw before in dig command output.
  2. Wildcard * domain i.e. any other subdomain will be also resolved to my router’s IP. In fact you don’t need this, I just wanted to show that you have such possibility.
  3. The root domain and its subdomain www will be proxied (i.e. orange cloud icon) to The real IP of my router is hidden in this case and it’s protected form DDoS by CF.

Now you can check that root domain is resolved to CF proxy:

$ dig

; DiG 9.11.5-P1-1ubuntu2.3-Ubuntu
;; global options: +cmd
;; Got answer:
;; HEADER opcode: QUERY, status: NOERROR, id: 35463
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 4096
;			IN	A

;; ANSWER SECTION:		3600	IN	A		3600	IN	A

;; Query time: 51 msec
;; WHEN: Sun May 05 14:05:34 EEST 2019
;; MSG SIZE  rcvd: 94

The IP addresses and belongs to CloudFlare.

Configure uhttpd webserver to work with the dynamic domain

In fact, you can just use the DDNS directly in /etc/config/uhttpd instead of IP address i.e.:

config uhttpd homepage
  option realm homepage
  list listen_http ''
  option home '/tmp/www/'
  option rfc1918_filter '0'

Here I configured my homepage on 80 port but instead of my external IP address I just used my DDNS It’s important that while my domain is it refers to CloudFlare so I can’t use it and I have to use the DDNS.

When eth1 (i.e. wan) network interface is restarted it may receive a new IP. So we have to update our DDNS. We can add a hook on iface up and send the update. So we should trigger the same command that we put into cron. To do so you need to add a hook to /etc/hotplug.d/iface/

case "$ACTION" in

I set it’s prio to 97 to run it after 95-ddns script if you decided to use it instead of self made cron script. Just to avoid conflicts.

To restart uhttpd after external IP was changed you can add the hotplug script:

case "$ACTION" in
/etc/init.d/uhttpd enabled && sleep 30 && /etc/init.d/uhttpd restart

And put it to /etc/hotplug.d/iface/ We set 30 seconds delay to be sure that dns record was updated.

Now let’s try:

# ifconfig eth1 down
# ifconfig eth1
eth1      Link encap:Ethernet  HWaddr 00:C5:F4:71:1B:9A  
          BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:2488344 errors:0 dropped:499 overruns:0 frame:0
          TX packets:818007 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:3068100023 (2.8 GiB)  TX bytes:84736706 (80.8 MiB)
# ifconfig eth1 up
# ifconfig eht1
eth1      Link encap:Ethernet  HWaddr 00:C5:F4:71:1B:9A  
          inet addr:  Bcast:  Mask:
          inet6 addr: fe80::2c5:f4ff:fe71:1b9a/64 Scope:Link
          RX packets:2487401 errors:0 dropped:499 overruns:0 frame:0
          TX packets:817808 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:3068008637 (2.8 GiB)  TX bytes:84672103 (80.7 MiB)

# ps | grep uhttpd
 3007 root      1296 S    /usr/sbin/uhttpd -f -h /www -r main -x /cgi-bin -p
 3008 root      1296 S    /usr/sbin/uhttpd -f -h /tmp/www/ -r homepage1 -p
 3018 root      1200 S    grep uhttpd


  1. Stop eth1 interface. At this moment internet goes down.
  2. See eth1 details to be sure that there is no external IP.
  3. Start eth1 with ifconfig eth1 up and see it’s details that IP now obtained.
  4. Check that utthpd process is restarted after 5 seconds. To ensure that it was restarted you can change site name or realm in /etc/confg/uhttpd and then see that the name was changed after restart. Here for example you might note that I changed homepage realm name to homepage1.

In fact, we don’t have to restart the uhttpd if IP wasn’t changed. Also if we detected IP change then we can start uhttpd with the new IP. For example we can update it with uci. It’s not so easy to get ip from interface name but you can see getLocalIp function from ddns scripts

But this solution is much simpler so I decided to keep it.

Protect router from hackers: allow access to HTTP server only to CloudFlare proxy IPs

Since my website is accessible only from CloudFlare so I need to allow CF IPs but deny any others. I denied access to 80 port from /etc/config/firewall file but to allow CF IPs you need to add this script to /etc/firewall.user:

for ip in `wget -qO-`; do
  iptables -I INPUT -p tcp -m multiport --dports http,https -s $ip -j ACCEPT

The script will fetch a list of CF IPs and allow them via iptables.


How do I set up Google Talk/Hangout in Pidgin?

Google Talk still works with XMPP so you can use Pidgin and there is an official tutorial Configure Pidgin to connect to Google Talk

But there is missing important part: you’ll get «Not Authorized» error during connect.
The problem is that Google tries to improve security so your Google/Gmail password ideally should never be entered anywere except of Google itself so no any trojan or virus can’t stole your password.
So how can you login into Google Talk without inputting a password from Google Account?
Well, you can generate another password.
Go to My Account, and in the «Sign-in & Security» column, go to Signing in to Google and then App passwords

You’ll see the «Select the app and device you want to generate the app password for.»
Select App: Mail
Select Device: Other
Then input «pidgin»
Press Generate button

Then you’ll see «Your app password for your device»


Copy the generated password and input it into Pidgin account and that’s it.

Nginx Plus Docker Image + lua-resty-openidc for OAuth termination

I need a Docker image with Nginx Plus and configured lua-resty-openidc to use Keycloak OAuth provider.
I made it based on this article Deploying NGINX and NGINX Plus with Docker but there was few additional non trivial steps so here is my result.
Create a folder with nginx plus repo keys (nginx-repo.crt and nginx-repo.key)
Then create a Dockerfile with the following content:

FROM ubuntu:artful

# Download certificate and key from the customer portal (
# and copy to the build context
COPY nginx-repo.crt /etc/ssl/nginx/
COPY nginx-repo.key /etc/ssl/nginx/

# Install NGINX Plus
RUN set -x \
  && apt-get update && apt-get upgrade -y \
  && apt-get install --no-install-recommends --no-install-suggests -y apt-transport-https ca-certificates \
  && apt-get install -y lsb-release wget \
  && wget && apt-key add nginx_signing.key \
  && wget -q -O /etc/apt/apt.conf.d/90nginx \
  && printf "deb `lsb_release -cs` nginx-plus\n" | tee /etc/apt/sources.list.d/nginx-plus.list \
  && apt-get update && apt-get install -y nginx-plus nginx-plus-module-lua nginx-plus-module-ndk luarocks libssl1.0-dev git

RUN set -x \
  && apt-get remove --purge --auto-remove -y \
  && rm -rf /var/lib/apt/lists/*

# Forward request logs to Docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
  && ln -sf /dev/stderr /var/log/nginx/error.log

RUN luarocks install lua-resty-openidc
RUN luarocks install lua-cjson
RUN luarocks install lua-resty-string
RUN luarocks install lua-resty-http
RUN luarocks install lua-resty-session
RUN luarocks install lua-resty-jwt



CMD ["nginx", "-g", "daemon off;"]

Here you can see that we installing not only nginx-plus but also nginx-plus-module-lua and nginx-plus-module-ndk modules which are needed to run lua-resty-openidc.
Since open lua-resty-openidc is distributed via luarocks package manager we need to install it too and then install all needed packages via luarocks. For lua-crypto dependency you need to install libssl1.0-dev package with OpenSSL headers and for some other package we needed git, don't ask me why, I have no idea.
FYI: openidc is installed into file /usr/local/share/lua/5.1/resty/openidc.lua

Then you need to build an image with

docker build --no-cache -t nginxplus .

If you have a Docker Registry inside your company you can publish the image there:

docker tag nginxplus your.docker.registry:5000/nginxplus
docker push your.docker.registry:5000/nginxplus

Not you have an image and you can run it. All you need is to mount you server config into /etc/nginx folder. Consider you have a docker-compose.yml file with the following content:

version: '3'
    image: your.docker.registry:5000/nginxplus
    container_name: gw-nginx
      - ~/gateway/etc/nginx/nginx.conf:/etc/nginx/nginx.conf
      - ~/gateway/etc/nginx/conf.d/:/etc/nginx/conf.d/
      - /etc/localtime:/etc/localtime
      - 80:80
    image: jboss/keycloak
    container_name: gw-keycloak
      - /etc/localtime:/etc/localtime
      - 8080:8080
      - KEYCLOAK_USER=root
      - KEYCLOAK_PASSWORD=changeMePlease

Now create a gateway folder:
mkdir ~/gateway
cd ~/gateway
And plase nginx.conf file into ~/gateway/etc/nginx/nginx.conf. The most important is to place this two lines:

http {
  resolver yourdnsip;
  lua_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
  lua_ssl_verify_depth 5;

For some reason without this lines Lua scripts can’t resolve upstreams.

Create ~/gateway/etc/nginx/conf.d/default.conf file and configure it as descibed in restly-openidc documentation.
Finally you can run it with docker-compose up -d command.

[Linkset] Authorization termination: OAuth reverse proxy

UPDATE Today was released Nginx Plus with a new nginx-openid-connect module.

If you are looking for Authentication Server or OAuth library then OpenID Conect implementations page is a good place to start. I chose Keycloak but also want to look on FreeIPA or

Keycloak Security Proxy but I want proxy as Nginx module and I need to implement something non standard.
Also there is some an OpenID/Keycloak Proxy service

For Apache web server everything is clear:

But I need something for Nginx .

lua-resty-openidc and it’s intro blog post written in Lua so its easier to extend (so I did for implementing of custom grant flow+ sessions + reference tokens). written fully in C++ and this is interesting because you don’t need to enable Lua on Nginx (believe me, this can be harmful).

What is also interesting is tha module for only one purpose: to use reference tokens (opaque tokens) it’s written in C so no needs for additional deps.

Authentication Based on Subrequest Result

Actually Nginx already has something that can satisfy 80% of your needs:

But to use the sebrequest auth your auth server should support this or you need to setup another shim proxy:
and here is docker container which integrates it

Or you can use this one which is written in Lua

Important article from Security researcher Egor Homakov who hacked several times GitHub and Facebook:

OAuth1, OAuth2, OAuth…?

[Linkset] Payment System, Payment Gateway and Credit Card Icons SVG logos in flat, mono,outline and usual variants. Has Verve credit card logo most bigest collection of logos of popular brands, not only payments systems. SVG. See their demo site collection which has icons for some cryptocurrencies. SVG payment system logos as font. WOFF format list of payment logos in different dimensions but PNG only SVG and has CSS sprite SVG PNG with many variants
Flexible SVG credit card logo assets and CSS Library with credit card icons for Material-UI payment gateway and credit card logo icons. Available in 4 sizes. Has Bitcoin logo and Contactless Payment Logo

How to expose locally service to Internet

Don’t remove folders from your app, just clear all inner files

Dear programmers, if your application uses some folders please never delete the folders and just clear all inner files instead.
Because users may symlink any folder to another disk (e.g. RAM) or chmod them with different privileges.
For example, Maven always removes a target folder when calling mvn clean install and this creates a problem for me because I mapped the target folder to RAM disk.
This is not a rule to follow, but something that you may think about during development.