Schematik build

How to Build an ESP32 Security Camera

Stream a tiny ESP32-CAM over your local Wi-Fi network

Schematik

Published May 12, 2026 · Updated July 22, 2026

ESP32Beginner25 minutes5 components4 assembly steps
Remix this project
Photo of How to Build an ESP32 Security Camera

What you'll build

This guide turns the Ai-Thinker ESP32-CAM board into a local Wi-Fi security camera. Its OV2640 camera signal pins are fixed PCB traces, so there is no camera breakout to wire. The external flashing hardware is the SparkFun Beefy 3 FTDI programmer and a Micro USB data cable.

Once the sketch is deployed, the board joins your 2.4 GHz Wi-Fi network and prints a local address in Serial Monitor. Open that address from any browser on the same network and you get a simple page that refreshes live captures from the camera every 700 milliseconds.

The stream stays local and the code stays simple. It is a practical starting point before adding authentication, motion detection, a battery, or a more permanent mount. This project came from Sam's Schematik Short, which you can watch on YouTube.

What you are building

This guide covers a local-network camera only. The firmware has four jobs:

  1. initialise the OV2640 with the AI Thinker pin map,
  2. connect to your 2.4 GHz Wi-Fi network using YOUR_WIFI_SSID and YOUR_WIFI_PASSWORD,
  3. serve a root page that auto-refreshes captures at /capture every 700 ms,
  4. return JPEG frames from the camera buffer on each request to /capture.

Push notification, authentication, cloud storage, and motion detection are out of scope.

Upload and calibrate

Open the sketch in Schematik and set the two Wi-Fi placeholders before deploying:

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

Deploy from the Schematik project page, then open Serial Monitor at 115200 baud. After the board connects you will see:

Security camera ready
Open http://192.168.x.x

Open that address in a browser on the same 2.4 GHz network. The page loads a JPEG image that refreshes automatically. If the frame rate feels slow, change FRAMESIZE_VGA to FRAMESIZE_QVGA in the camera config for a faster but smaller image.

Do not commit or share the sketch with real Wi-Fi credentials in it.

Troubleshooting

  • "Camera init failed" in Serial Monitor. The board type is probably wrong. Select the AI Thinker ESP32-CAM profile, not a generic ESP32 DevKit. Also check that the camera ribbon is fully clipped into the socket.
  • No serial port appears after connecting USB. The cable is likely charge-only. Swap it for a cable that transfers data — the port should appear in the OS device list immediately.
  • Board does not enter upload mode. Make sure IO0 is connected to GND before pressing upload, then remove the link before resetting.
  • ESP32-CAM resets or drops connection during capture. Confirm the camera uses the regulated 5 V rail, Beefy 3 VCC is disconnected, and all grounds are shared.
  • Browser page loads but image is black or corrupted. Power-cycle the board. The OV2640 occasionally needs a clean reset to initialise correctly after a flash.
  • "Connection refused" when opening the IP address. The board has not finished connecting to Wi-Fi. Wait a few seconds and refresh; Serial Monitor will show the IP once the server starts.

Going further

Once the basic stream is reliable, the natural next step is to add a motion-detection pass in the capture loop: compare consecutive JPEG buffers and trigger an alert when enough pixels change. Pair that with an MQTT publish or a simple HTTP webhook call and you have a basic event-driven camera without any external cloud dependency.

For a version that sets itself up without editing credentials and supports recovery without reflashing, see the ESP32-CAM Setup Portal Security Camera guide, which adds a browser-based Wi-Fi configuration page and a power-cycle reset path.

Wiring diagram

Interactive · read-only

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
Ai-Thinker ESP32-CAM with OV26401Ai-Thinker ESP32-CAM board with OV2640 and the standard AI-Thinker camera pin map.
SparkFun Beefy 3 FTDI programmer (3.3 V UART only)1Programs the ESP32-CAM with 3.3 V UART signals. Its VCC pin is intentionally left disconnected; the camera uses the separate 5 V supply.
5 V, 4 A USB-C wall power supply with switch1Sized for the LED load; grounds must be common with the controller.
USB-C socket breakout for 5 V and GND1Breaks the supply output out to safe wired 5 V and GND rails.
Micro USB data cable (not charge-only)1A small USB adapter/base board used to flash and power the ESP32-CAM from the browser.

Assembly

4 steps
  1. Seat the ESP32-CAM on the USB adapter

    Align the ESP32-CAM headers with the programmer adapter and press the board down evenly. Keep the camera ribbon seated and avoid touching the lens.

    • Tip: The Short uses a tiny USB adapter, so you do not need loose jumper wires for this version.
    • Tip: If your adapter has boot/upload buttons, leave them accessible while flashing.
    • Unplug USB before reseating the ESP32-CAM on the adapter. Offset headers can short 5V into the wrong pin.
  2. Connect USB and choose the ESP32-CAM board

    Connect the adapter to your computer with a USB data cable. In Schematik or the Arduino tooling, use the AI Thinker ESP32-CAM style pinout unless your module says otherwise.

    • Tip: Open the serial monitor at 115200 baud so you can read the local IP address after upload.
    • Tip: If no port appears, try another USB cable before changing code.
    • Tip: The camera signal pins are built into the ESP32-CAM module. Do not add jumper wires for the camera itself.
    • Some charge-only USB cables power the board but cannot upload sketches.
  3. Add Wi-Fi details and deploy

    Replace YOUR_WIFI_SSID and YOUR_WIFI_PASSWORD in the sketch, then deploy it to the ESP32-CAM. The code starts a small web server on your local network.

    • Tip: Use a 2.4 GHz Wi-Fi network. Most ESP32-CAM modules cannot join 5 GHz-only networks.
    • Tip: Keep the board close to the router for the first test.
    • Do not commit or share a version of the sketch with real Wi-Fi credentials in it.
  4. Open the local camera page

    After the board restarts, read the IP address printed in serial output and open it in a browser on the same Wi-Fi network. The page refreshes camera captures to give you a simple live view.

    • Tip: If the page loads slowly, lower the frame size from FRAMESIZE_VGA to FRAMESIZE_QVGA.
    • Tip: Use this as a local prototype, not an internet-exposed security system.
    • Keep the camera stream on your private LAN unless you add authentication and HTTPS.

Pin assignments

Board wiring reference
PinConnectionType
GPIO 0esp32-cam-camera-1 XCLKdigital
GPIO 26esp32-cam-camera-1 SIODi2c
GPIO 27esp32-cam-camera-1 SIOCi2c
GPIO 5esp32-cam-camera-1 Y2data
GPIO 18esp32-cam-camera-1 Y3data
GPIO 19esp32-cam-camera-1 Y4data
GPIO 21esp32-cam-camera-1 Y5data
GPIO 36esp32-cam-camera-1 Y6data
GPIO 39esp32-cam-camera-1 Y7data
GPIO 34esp32-cam-camera-1 Y8data
GPIO 35esp32-cam-camera-1 Y9data
GPIO 25esp32-cam-camera-1 VSYNCdata
GPIO 23esp32-cam-camera-1 HREFdata
GPIO 22esp32-cam-camera-1 PCLKdata
GPIO 32esp32-cam-camera-1 PWDNdigital
EXTesp32-cam-camera-1 5Vusb-c-power-breakout:VBUSpower
EXTesp32-cam-camera-1 GNDusb-c-power-breakout:GNDground
GPIO 3esp32-cam-camera-1 U0Rupload-uart
GPIO 1esp32-cam-camera-1 U0Tupload-uart
GPIO 0esp32-cam-camera-1 IO0upload-mode
EXTesp32-security-camera-beefy3-programmer-1 GNDesp32-cam-camera-module:GNDground
GPIO 3esp32-security-camera-beefy3-programmer-1 TXOesp32-cam-camera-module:U0Rupload-uart
GPIO 1esp32-security-camera-beefy3-programmer-1 RXIesp32-cam-camera-module:U0Tupload-uart
EXTesp32-security-camera-regulated-5v-supply-1 5V_OUTusb-c-power-breakout:VBUSpower
EXTesp32-security-camera-regulated-5v-supply-1 GNDusb-c-power-breakout:GNDground
5Vesp32-security-camera-usb-c-power-breakout-1 VBUSpower
GNDesp32-security-camera-usb-c-power-breakout-1 GNDground

Firmware

ESP32
schematik_esp32.inoDeploy to device
#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>

const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

WebServer server(80);

void handleRoot() {
  server.send(200, "text/html",
    "<!doctype html><html><head><meta name='viewport' content='width=device-width, initial-scale=1'>"
    "<title>ESP32 Security Camera</title>"
    "<style>body{font-family:sans-serif;background:#111;color:#fff;text-align:center;margin:0;padding:2rem}"
    "img{width:100%;max-width:640px;border-radius:16px}</style></head>"
    "<body><h1>ESP32 Security Camera</h1><img src='/capture' id='cam'>"
    "<script>setInterval(()=>{document.getElementById('cam').src='/capture?t='+Date.now()},700)</script>"
    "</body></html>"
  );
}

void handleCapture() {
  camera_fb_t *fb = esp_camera_fb_get();
  if (!fb) {
    server.send(500, "text/plain", "Camera capture failed");
    return;
  }

  server.sendHeader("Cache-Control", "no-store");
  server.send_P(200, "image/jpeg", (const char *)fb->buf, fb->len);
  esp_camera_fb_return(fb);
}

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(false);
  delay(300);

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_VGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  if (esp_camera_init(&config) != ESP_OK) {
    Serial.println("Camera init failed. Check the board type and camera ribbon.");
    return;
  }

  WiFi.begin(ssid, password);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  server.on("/", handleRoot);
  server.on("/capture", handleCapture);
  server.begin();

  Serial.println();
  Serial.println("Security camera ready");
  Serial.print("Open http://");
  Serial.println(WiFi.localIP());
}

void loop() {
  server.handleClient();
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik