diff --git a/libraries/Update/src/Update.h b/libraries/Update/src/Update.h
index abceeb0996b..303ec7cc4cc 100644
--- a/libraries/Update/src/Update.h
+++ b/libraries/Update/src/Update.h
@@ -28,6 +28,9 @@
 
 #define ENCRYPTED_BLOCK_SIZE 16
 
+#define SPI_SECTORS_PER_BLOCK   16      // usually large erase block is 32k/64k
+#define SPI_FLASH_BLOCK_SIZE    (SPI_SECTORS_PER_BLOCK*SPI_FLASH_SEC_SIZE)
+
 class UpdateClass {
   public:
     typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
@@ -166,6 +169,7 @@ class UpdateClass {
     bool _verifyHeader(uint8_t data);
     bool _verifyEnd();
     bool _enablePartition(const esp_partition_t* partition);
+    bool _chkDataInBlock(const uint8_t *data, size_t len) const;    // check if block contains any data or is empty
 
 
     uint8_t _error;
diff --git a/libraries/Update/src/Updater.cpp b/libraries/Update/src/Updater.cpp
index f9ecbfb057f..30d88cb8ff1 100644
--- a/libraries/Update/src/Updater.cpp
+++ b/libraries/Update/src/Updater.cpp
@@ -203,14 +203,23 @@ bool UpdateClass::_writeBuffer(){
     if (!_progress && _progress_callback) {
         _progress_callback(0, _size);
     }
-    if(!ESP.partitionEraseRange(_partition, _progress, SPI_FLASH_SEC_SIZE)){
-        _abort(UPDATE_ERROR_ERASE);
-        return false;
+    size_t offset = _partition->address + _progress;
+    bool block_erase = (_size - _progress >= SPI_FLASH_BLOCK_SIZE) && (offset % SPI_FLASH_BLOCK_SIZE == 0);             // if it's the block boundary, than erase the whole block from here
+    bool part_head_sectors = _partition->address % SPI_FLASH_BLOCK_SIZE && offset < (_partition->address / SPI_FLASH_BLOCK_SIZE + 1) * SPI_FLASH_BLOCK_SIZE;    // sector belong to unaligned partition heading block
+    bool part_tail_sectors = offset >= (_partition->address + _size) / SPI_FLASH_BLOCK_SIZE * SPI_FLASH_BLOCK_SIZE;     // sector belong to unaligned partition tailing block
+    if (block_erase || part_head_sectors || part_tail_sectors){
+        if(!ESP.partitionEraseRange(_partition, _progress, block_erase ? SPI_FLASH_BLOCK_SIZE : SPI_FLASH_SEC_SIZE)){
+            _abort(UPDATE_ERROR_ERASE);
+            return false;
+        }
     }
-    if (!ESP.partitionWrite(_partition, _progress + skip, (uint32_t*)_buffer + skip/sizeof(uint32_t), _bufferLen - skip)) {
+
+    // try to skip empty blocks on unecrypted partitions
+    if ((_partition->encrypted || _chkDataInBlock(_buffer + skip/sizeof(uint32_t), _bufferLen - skip)) && !ESP.partitionWrite(_partition, _progress + skip, (uint32_t*)_buffer + skip/sizeof(uint32_t), _bufferLen - skip)) {
         _abort(UPDATE_ERROR_WRITE);
         return false;
     }
+
     //restore magic or md5 will fail
     if(!_progress && _command == U_FLASH){
         _buffer[0] = ESP_IMAGE_HEADER_MAGIC;
@@ -389,4 +398,20 @@ const char * UpdateClass::errorString(){
     return _err2str(_error);
 }
 
+bool UpdateClass::_chkDataInBlock(const uint8_t *data, size_t len) const {
+    // check 32-bit aligned blocks only
+    if (!len || len % sizeof(uint32_t))
+        return true;
+
+    size_t dwl = len / sizeof(uint32_t);
+
+    do {
+        if (*(uint32_t*)data ^ 0xffffffff)      // for SPI NOR flash empty blocks are all one's, i.e. filled with 0xff byte
+            return true;
+
+        data += sizeof(uint32_t);
+    } while (--dwl);
+    return false;
+}
+
 UpdateClass Update;