I was browsing the other night looking for project ideas when I came across a question on the official Arduino forums posted back in 2015. The question was “How to use wordpress to control servo?”—a question which with my web background sparked my interest.

I’ll admit that I thought this one would make a nice easy little project, although that turned out to be very wrong—with the process of having a user interact with a servo from a WordPress site being a more involved that I had initial thought.
The project in action
Before we get into explaining how it works and what’s needed for this project, you can see it below in action.
It’s fairly basic at the moment, being very much a proof of concept—and right now, I’m not sure what to do with it. If you’ve got any ideas or suggestions, please drop them in the comments!
What you’ll need
- NGINX server
If you don’t have one, you can setup an Amazon EC2 with AWS—it’s free for 12 months. Setting your server up is outside of the scope of this post, but there’s a pretty good getting started guide over at the official NGINX website for anyone who needs help with this. - SSL certificate
Your NGINX server will need an SSL certificate—I use namecheap for mine, as they’re pretty reasonable and offer good customer support, but there are of course plenty of other providers to choose from. Namecheap also have a pretty good guide for setting up an SSL on your NGINX server. - Dynamic DNS provider
If youre Nodemcu Websocket server lives on your desk, behind a residential ISP, you’re going to need a Dynamic DNS provider to deal with the issue of dynamic IP addresses. After a bit of research, I went with NOIP, who have an API to update your external IP address programmatically and a free tier that’s suitable for this project. - Admin access to your router
As pare of the Dynamic DNS set up, you’re going to need access to your router to configure port forwarding. This is another topic we won’t cover in any depth here, so if you need help I’d suggest checking out NOIP’s port forwarding guide. - 2 x SG90 micro server
- 1 x Nodemcu
- Some jumper cables
Wiring guide
The wiring is pretty simple—it’s just two micro servos without an external power supply. Depending on what I decide to do with this project, I’ll likely add a power supply and maybe a couple of extra servos.
Right now, this project was really just a proof of concept, so it was more about getting the basics ironed out, as opposed to really building anything.
If you need a reference on how to put this all together, see the diagram I’ve provided below:

The code
I’ve tried to make this section as simple and straightforward as I could, breaking it down into Dynamic DNS, Nodemcu, WordPress, and NGINX.
I wasn’t sure in which order to attack these, so apologies if anything is a little unclear. If you have any questions, please drop me a comment and I’ll do my best to provide more clarity where needed.
Dynamic DNS
While not strictly code—although there’s a few lines to add to your .ino to programmatically update your dynamic IP address—we’ll start with setting up Dynamic DNS, as it’s an essential first step to getting this all up and running.
As I said at the beginning, I went and signed up for NOIP—both because the NOIP API is pretty simple and they have a free tier, making it ideal for anyone just looking to test and try this out. You can signup for a NOIP account here, once done you’ll want to follow their getting started guide and port forwarding guide to set it all up.
You aren’t going to need to use an SSL on the DDNS that you setup, so don’t worry about that—just make a note of your username (email), password, and hostname, as you’ll need these for the HTTP GET requests that update your dynamic IP address.
Nodemcu
Things you’ll want to set—snippets given below for reference:
1. A local static IP address for your device, which you’ll need for setting up port forwarding.
IPAddress local_IP(YOUR_STATIC_LOCAL_IP);
2. Your network SSID and password
WiFiMulti.addAP("YOUR_NETWORK_SSID", "YOUR_NETWORK_PASSWORD");
3. Your NOIP details, used to update your external dynamic IP address each time you turn the device on.
There are a few things to note here, most importantly the “Authorization”—your username and password need to be Base 64 encoded. For example, “joe@blogs.com:password” would be “am9lQGJsb2dzLmNvbTpwYXNzd29yZA==”—you can use an online Base64 encoder for this, like the one available at OpinionatedGeek.
client.println("GET /nic/update?hostname=YOUR_NOIP_HOSTNAME HTTP/1.0");
client.println("Host: dynupdate.no-ip.com");
client.println("Authorization: Basic YOUR_USERNAME:PASSWORD_BASE64_ENCODED");
client.println("User-Agent: Nodemcu Sketch/1.0 YOUR_EMAIL_ADDRESS");
With that out of the way, the full code can be found below. You can also download the .ino from my Google Drive.
server.ino
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include <Servo.h>
ESP8266WiFiMulti WiFiMulti;
IPAddress local_IP(YOUR_STATIC_LOCAL_IP);
WebSocketsServer webSocket = WebSocketsServer(81);
StaticJsonDocument<300> doc;
#define USE_SERIAL Serial
Servo servo1;
Servo servo2;
int servo1Pin = 2;
int servo1Pos = 180;
int servo2Pin = 4;
int servo2Pos = 180;
void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
USE_SERIAL.printf("[%u] Disconnected!\n", num);
break;
case WStype_CONNECTED:
{
IPAddress ip = webSocket.remoteIP(num);
USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
String connectedMsg = "{\"Servo1Pos\":" + String(abs((servo1.read())-180)) +",\"Servo2Pos\":" + String(abs((servo2.read())-180)) + "}";
webSocket.sendTXT(num, connectedMsg);
}
break;
case WStype_TEXT:
{
DeserializationError error = deserializeJson(doc, payload);
if (error) {
USE_SERIAL.print(F("deserializeJson() failed: "));
USE_SERIAL.print(error.f_str());
return;
}
String servo = doc["Servo"];
String servoPos = doc["Pos"];
int Pos = abs((servoPos.toInt())-180);
if(servo == "1") {
servo1.write(Pos);
}
else if(servo == "2") {
servo2.write(Pos);
}
}
}
}
void setup() {
USE_SERIAL.begin(9600);
USE_SERIAL.setDebugOutput(true);
USE_SERIAL.println();
USE_SERIAL.println();
USE_SERIAL.println();
for(uint8_t t = 4; t > 0; t--) {
USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
USE_SERIAL.flush();
delay(1000);
}
servo1.attach(servo1Pin, 500, 2400);
servo2.attach(servo2Pin, 500, 2400);
servo1.write(servo1Pos);
servo2.write(servo2Pos);
WiFiMulti.addAP("YOUR_NETWORK_SSID", "YOUR_NETWORK_PASSWORD");
while(WiFiMulti.run() != WL_CONNECTED) {
delay(100);
}
WiFiClient client;
if (client.connect("dynupdate.no-ip.com", 80)) {
Serial.println("connected");
while(client.connected())
{
client.println("GET /nic/update?hostname=YOUR_NOIP_HOSTNAME HTTP/1.0");
client.println("Host: dynupdate.no-ip.com");
client.println("Authorization: Basic YOUR_USERNAME:PASSWORD_BASE64_ENCODED");
client.println("User-Agent: Nodemcu Sketch/1.0 YOUR_EMAIL_ADDRESS");
client.println();
while(client.available())
{
char read_char = client.read();
Serial.write(read_char);
}
}
client.stop();
}
else {
Serial.println("connection failed");
}
webSocket.begin();
webSocket.onEvent(webSocketEvent);
}
void loop() {
webSocket.loop();
}
WordPress
I wrote a simple browser Websocket client in Javascript and wrapped in in a plugin, which embeds it in WordPress as an iframe using a shortcode. It’s not very complicated (and might even be seen as cheating by some)—but it’s hands-down the easiest approach and works well.
I’ve included the code below for both the html and php files that make up the plugin, but you can also download these here. To use it, you’ll want to edit the Websocket address in the index.html—snippet below for reference:
const socket = new WebSocket('YOUR_WEBSOCKET_URL');
After that, simply put both files in a .zip archive and upload it to WordPress as you would any other plugin, on the WP admin plugins page, activating it once uploaded.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width-device-width, initial-scale=1.0">
<title>Servo Websocket Client</title>
<style>
.inline { float:left; }
.clear { clear:both; }
.servo { width: 250px; margin: 0 10px; }
.servo h2 { text-align: center; }
.servo .position input {
height: 114px;
width: 122px;
line-height: 114px;
font-size: 46px;
text-align: center;
}
.servo .control {
width: 0px;
height: 0px;
border-top: 60px solid transparent;
border-bottom: 60px solid transparent;
cursor: pointer;
}
.servo .control.arrow-left { border-right: 60px solid #008184; }
.servo .control.arrow-right { border-left: 60px solid #008184; }
</style>
</head>
<body>
<div class="container">
<div class="servo inline" data-servo="1">
<h2>Servo 1</h2>
<div class="control decrement arrow-left inline"></div>
<div class="position"><input value="0" class="inline" disabled></div>
<div class="control increment arrow-right inline"></div>
<div class="clear"></div>
</div>
<div class="servo inline" data-servo="2">
<h2>Servo 2</h2>
<div class="control decrement arrow-left inline"></div>
<div class="position"><input value="0" class="inline" disabled></div>
<div class="control increment arrow-right inline"></div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
</body>
<script>
const socket = new WebSocket('YOUR_WEBSOCKET_URL');
socket.addEventListener('open', function(e) {
socket.send('Hello Server!');
});
socket.addEventListener('message', function (e) {
const connectedServoPos = JSON.parse(e.data);
document.querySelector('.servo[data-servo="1"] .position input').value = connectedServoPos.Servo1Pos.toString();
document.querySelector('.servo[data-servo="2"] .position input').value = connectedServoPos.Servo2Pos.toString();
})
const sendMessage = (servo, message) => {
socket.send(`{"Servo":${servo},"Pos":${message}}`);
}
const servoControls = document.querySelectorAll('.servo .control');
Array.from(servoControls).forEach(link => {
['mousedown','touchstart'].forEach( evt =>
link.addEventListener(evt, e => {
let servo = e.target.parentNode.dataset.servo;
let servoPosInput = e.target.parentNode.querySelector('input');
let servoPos = parseInt(servoPosInput.value);
changeServoPos = setInterval(function() {
if(e.target.classList.contains("increment")) {
if(servoPos >= 180) {
clearInterval(changeServoPos);
}
else {
servoPosInput.value = servoPos += 2;
sendMessage(servo, servoPos += 2);
}
}
else if(e.target.classList.contains("decrement")) {
if(servoPos <= 0) {
clearInterval(changeServoPos);
}
else {
servoPosInput.value = servoPos -= 2;
sendMessage(servo, servoPos -= 2);
}
}
}, 25);
}
));
['mouseup','touchend'].forEach( evt =>
link.addEventListener(evt, e => {
clearInterval(changeServoPos);
})
);
});
</script>
</html>
plugin.php
<?php
/**
* Plugin Name: Servo Websocket Client
* Plugin URI: https://www.arduinobits.com
* Description: Websocket Client for servo control project
* Version: 1.0
* Author: ArduinoBits
* Author URI: https://www.arduinobits.com
**/
function servo_websocket_iframe() {
return '<iframe border="0" class="shortcode_iframe" src="' .plugin_dir_url( __FILE__ ). 'index.html" width="600px" height="200px" scrolling="no" frameBorder="0"></iframe>';
}
add_shortcode('servo_control', 'servo_websocket_iframe');
NGINX
As I stated at the beginning of this post, we’re not going to get into the process of setting up an NGINX server and how to install and SSL certificate.
There are plenty of tutorials on these topics already, some of which I’ve included under the “What you’ll need” section of this post—so scroll back up if you’re looking for help on getting this part of the project up and running from scratch.
We’ll just cover the code you’ll need to setup the reverse proxy in your server block, which I’ve provided below. Simply update the snippet below with your own server name, SSL certificate CSR/key paths, and DDNS address, then drop it in your .conf file and restart NGINX.
/etc/nginx/sites-available/YOUR_SERVER_NAME.conf
server {
listen 443 ssl http2;
server_name YOUR_SERVER_NAME;
ssl_certificate YOUR_SSL_CERTIFICATE_PATH;
ssl_certificate_key YOUR_SSL_CERTIFCATE_KEY_PATH;
location / {
proxy_pass YOUR_DDNS_URL;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_read_timeout 86400;
}
}
Summing it up and next steps
This project was more about curiosity than anything else. After coming across the question, I really just wanted to know just how difficult it would be to rig something like this up. While it turns out that it was more difficult than I initially thought, with a fair few moving parts, it wasn’t too bad, and gave me a good opportunity to dabble with a few new things—like setting up an NGINX server and using Dynamic DNS, both things I hadn’t attempted before.
Unfortunately, it seems that the poster of the original question that kick started it all hasn’t been active on the forum for a few years—if you’re out there, I’d love to know more about your use case, reach out if you ever come across this post.
I’m not sure yet what to do with this. Since I started with the project, I’ve had a couple of ideas that could be fun for users visiting the site—but so far, I haven’t really had time to given them much thought or flesh them out, so I’ll keep them under my hat for now.
If you’ve got any thoughts or suggestions of your own, I’d love to hear them! Let me know in the comments. Thanks for reading!